Spring web开发教程之Request获取3种方式

 更新时间:2023年11月22日 16:06:23   作者:lzzyok  
这篇文章主要给大家介绍了关于Spring web开发教程之Request获取3种方式的相关资料,request对象是从客户端向服务器发出请求,包括用户提交的信息以及客户端的一些信息,需要的朋友可以参考下

前言

在开发 Java Web 项目中,我们经常使用 HttpServletRequest 获取请求参数、请求头等信息。在Spring项目,我们通常会使用 Spring 提供的注解获取参数,如 @RequestParam、@RequestHeader。

不过在某些场景下,我们可能需要从 HttpServletRequest 对象中取得更多的能力,如获取请求 IP,获取请求域名等。这篇我们来学习如何在 Spring MVC 环境下获取 HttpServletRequest,以及它们的实现方式,做到知其所以然。

Controller 方法参数

使用注解后的 Spring MVC controller 方法可以作为 handler 处理请求,如果想获取 request 对象,只需要在方法中添加 ServletRequest 或 HttpServletRequest 类型参数即可。代码如下

@RestController
public class UserController {

    @GetMapping("/getUser")
    public String getUser(HttpServletRequest request) {
        return "request ip is : " + request.getRemoteHost();
    }

}

扩展:如何要获取reponse,同例只要在方法中增加 ServletResponse 或 HttpServletResponse 类型参数即可。

Controller 方法参数实现原理

通过上面的代码我们很容易就实现了,那spring是怎么帮我们搞定的呢?

  • 在spring mvc中,所有浏览器发起的请求都会先交给DispatcherServlet 处理。
  • DispatcherServlet 根据用户或默认的配置使用 HandlerMapping 查找可处理请求的处理器。
  • DispatcherServlet 拿到 HandlerMapping 返回的处理器链 HandlerExecutionChain。整个处理器链包含拦截器和处理。
  • DispatcherServlet 将处理器适配为 HandlerAdapter。
  • DispatcherServlet 使用拦截器进行请求前置处理。
  • DispatcherServlet 使用处理器进行请求处理。
  • DispatcherServlet 使用拦截器进行请求后置处理。
  • DispatcherServlet 从拦截器或处理器中提取到模型及视图 ModelAndView。
  • DispatcherServlet 使用视图解析器 ViewResolver 解析视图出视图 View。
  • DispatcherServlet 渲染视图,响应请求返回给浏览器。

在上面第6步【DispatcherServlet 使用处理器进行请求处理】时,在调用我们自己的controller方法之前,Spring通过
HandlerMethodArgumentResolver向我们的controller方法注入对应的参数。

静态方法

除了通过 controller 方法参数获取 HttpServletRequest 对象,Spring 还允许通过其提供的工具类的静态方法来获取 HttpServletRequest。示例如下。

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

静态方法实现原理

上面的示例中,RequestContextHolder 表示一个请求上下文的持有者,内部将每个请求上下文信息存储到 ThreadLocal 中。

public abstract class RequestContextHolder {

  /**
   * 线程上下文 RequestAttributes
   */
  private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
      new NamedThreadLocal<>("Request attributes");

  /**
   * 支持继承的线程上下文 RequestAttributes
   */
  private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
      new NamedInheritableThreadLocal<>("Request context");

}

DispatcherServlet 处理请求前会将 request 存至 ServletRequestAttributes,然后放到 RequestContextHolder 中,具体可见DispatcherServlet的父类
FrameworkServlet.processRequest()。

直接注入

还可以将 HttpServletRequest 当做普通的 bean 注入。代码如下

@RestController
public class UserController {

    @Autowired
    private HttpServletRequest request;

    @GetMapping("/getIP")
    public String getIP() {
        return "request ip is : " + request.getRemoteHost();
    }

}

直接注入分析

通过 @Autowired 的方式引入 request 也很简单,想想这里会有问题吗?.......

Controller 不是一个单例 bean 对象吗?在一个 Spring 容器内只有一个实例,而每次请求都对应一个 request 对象,Spring 是怎样做到使用一个 request 表示多个请求的?

经过仔细分析,我们可以发现 Spring 注入 bean 时使用了底层的 
DefaultListableBeanFactory 获取 bean 实例,相关代码如下。

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
    implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
  // 不依赖关系
  private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);
   // 查找候选 bean
  protected Map<String, Object> findAutowireCandidates(
      @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {  
    //部分代码省略
    Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
    
    for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
      Class<?> autowiringType = classObjectEntry.getKey();
      if (autowiringType.isAssignableFrom(requiredType)) {
        Object autowiringValue = classObjectEntry.getValue();
        // 解析 ObjectFactory
        autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
        if (requiredType.isInstance(autowiringValue)) {
          result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
          break;
        }
      }
    }
    //部分代码省略
    }
}

DefaultListableBeanFactory 查找候选 bean 时会先从 resolvableDependencies 中查找,找到后调用 AutowireUtils.resolveAutowiringValue方法再次解析。

resolvableDependencies中对象是 Spring 中特殊的存在,不属于 Spring 管理的 bean,需要手动注册到 
DefaultListableBeanFactory。

我们继续跟踪源码。

abstract class AutowireUtils {
  public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
    if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
      // ObjectFactory 类型值和所需类型不匹配,创建代理对象
      ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
      if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
        // 创建代理对象,可用于处理 HttpServletRequest 注入等问题
        autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
            new Class<?>[]{requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
      } else {
        return factory.getObject();
      }
    }
    return autowiringValue;
  }
}

当resolvableDependencies中对象是ObjectFactory 类型,并且与所需的类型不匹配,Spring 使用 ObjectFactory 创建了一个 JDK 代理对象:

private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {

    private final ObjectFactory<?> objectFactory;

    public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
      this.objectFactory = objectFactory;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       try {
        return method.invoke(this.objectFactory.getObject(), args);
      } catch (InvocationTargetException ex) {
        throw ex.getTargetException();
      }
    }
  }

代理的实现简单,每当所需类型的方法调用时,就调用 ObjectFactory 中获取的实例对象的对应方法。

那怎么与HttpServletRequest关联启来呢?

Spring 在启动时会注册 Web 环境相关的依赖对象

public abstract class WebApplicationContextUtils {

  public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
                          @Nullable ServletContext sc) {

    beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
    beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
    if (sc != null) {
      ServletContextScope appScope = new ServletContextScope(sc);
      beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
      // Register as ServletContext attribute, for ContextCleanupListener to detect it.
      sc.setAttribute(ServletContextScope.class.getName(), appScope);
    }
    // ServletRequest 类型对应 ObjectFactory 注册
    beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
    beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
    beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
    beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
    if (jsfPresent) {
      FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
    }
  }
  
}

可以看到:Spring 为 ServletRequest 注入的是 RequestObjectFactory 类型,那再看看它的实现:

public abstract class WebApplicationContextUtils {

  private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {

    @Override
    public ServletRequest getObject() {
      return currentRequestAttributes().getRequest();
    }
	/**
	 * Return the current RequestAttributes instance as ServletRequestAttributes.
	 * @see RequestContextHolder#currentRequestAttributes()
	 */
	private static ServletRequestAttributes currentRequestAttributes() {
		RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
		if (!(requestAttr instanceof ServletRequestAttributes)) {
			throw new IllegalStateException("Current request is not a servlet request");
		}
		return (ServletRequestAttributes) requestAttr;
	}
    @Override
    public String toString() {
      return "Current HttpServletRequest";
    }
  }
}

可以看到,和前面介绍的【静态方法】思路一样。

以上就是3种在spring场景中,获取request的方法,get到了吗?

总结

到此这篇关于Spring web开发教程之Request获取3种方式的文章就介绍到这了,更多相关Spring web获取Request内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java进程内缓存框架EhCache详解

    Java进程内缓存框架EhCache详解

    这篇文章主要介绍了Java进程内缓存框架EhCache,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2021-12-12
  • spring boot集成smart-doc自动生成接口文档详解

    spring boot集成smart-doc自动生成接口文档详解

    这篇文章主要介绍了spring boot集成smart-doc自动生成接口文档详解,smart-doc是一款同时支持java restful api和Apache Dubbo rpc接口文档生成的工具,smart-doc颠覆了传统类似swagger这种大量采用注解侵入来生成文档的实现方法
    2022-09-09
  • Java数据结构及算法实例:朴素字符匹配 Brute Force

    Java数据结构及算法实例:朴素字符匹配 Brute Force

    这篇文章主要介绍了Java数据结构及算法实例:朴素字符匹配 Brute Force,本文直接给出实例代码,代码中包含详细注释,需要的朋友可以参考下
    2015-06-06
  • java实现三角形分形山脉

    java实现三角形分形山脉

    这篇文章主要为大家详细介绍了java实现三角形分形山脉,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • 详解JUC 常用4大并发工具类

    详解JUC 常用4大并发工具类

    这篇文章主要介绍了JUC 常用4大并发工具类的相关资料,帮助大家更好的理解和学习Java 并发编程,感兴趣的朋友可以了解下
    2020-10-10
  • Java编译错误问题:需要class,interface或enum

    Java编译错误问题:需要class,interface或enum

    这篇文章主要介绍了Java编译错误问题:需要class,interface或enum,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • 使用jenkins配置maven和JDK

    使用jenkins配置maven和JDK

    这篇文章主要介绍了使用jenkins配置maven和JDK问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • Java Web请求与响应实例详解

    Java Web请求与响应实例详解

    这篇文章主要介绍了Java Web请求与响应实例详解的相关资料,需要的朋友可以参考下
    2016-05-05
  • Java详细分析讲解自动装箱自动拆箱与Integer缓存的使用

    Java详细分析讲解自动装箱自动拆箱与Integer缓存的使用

    装箱就是把基本类型转换成包装类,拆箱就是把包装类转换成基本类型,下面这篇文章主要给大家介绍Java中自动装箱、自动拆箱与Integer缓存,需要的朋友可以参考下
    2022-04-04
  • java处理日期的工具类DateUtil

    java处理日期的工具类DateUtil

    这篇文章主要为大家详细介绍了java处理日期的工具类DateUtil,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-10-10

最新评论