Spring web开发教程之Request获取3种方式
前言
在开发 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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
spring boot集成smart-doc自动生成接口文档详解
这篇文章主要介绍了spring boot集成smart-doc自动生成接口文档详解,smart-doc是一款同时支持java restful api和Apache Dubbo rpc接口文档生成的工具,smart-doc颠覆了传统类似swagger这种大量采用注解侵入来生成文档的实现方法2022-09-09Java数据结构及算法实例:朴素字符匹配 Brute Force
这篇文章主要介绍了Java数据结构及算法实例:朴素字符匹配 Brute Force,本文直接给出实例代码,代码中包含详细注释,需要的朋友可以参考下2015-06-06Java编译错误问题:需要class,interface或enum
这篇文章主要介绍了Java编译错误问题:需要class,interface或enum,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2024-02-02Java详细分析讲解自动装箱自动拆箱与Integer缓存的使用
装箱就是把基本类型转换成包装类,拆箱就是把包装类转换成基本类型,下面这篇文章主要给大家介绍Java中自动装箱、自动拆箱与Integer缓存,需要的朋友可以参考下2022-04-04
最新评论