Spring中HttpServletRequest的注入机制解析
Spring中HttpServletRequest的注入机制解析
在Spring框架中,如何通过@Autowired注入HttpServletRequest?本文通过一个实际的代码审查场景,详细探讨了这一过程的实现机制,包括Spring如何处理外部对象的注入、线程安全问题以及相关源码的分析。
问题描述
在最近一次团队代码审查时,团队成员发现有将HttpServletRequest直接通过@Autowired注入的情况,于是大家产生了一个疑问:HttpServletRequest并非Spring中的类,且在没有手动通过@Bean的方式注入,Spring是怎么做到帮开发者完成注入的?同时,我们知道IoC容器中默认注入的Bean是单例,而每个请求都是独立的,这样不会出问题吗?
为了研究明白为什么,作者找了一些资料,并写了一个简单的Demo进行研究。
Demo类如下:
@RestController
public class HttpServletRequestTest {
@Autowired
private HttpServletRequest httpServletRequest;
@GetMapping("/sayHi")
public void sayHi(String name) {
System.out.println(httpServletRequest.getRequestURL().toString());
JSONObject res = new JSONObject();
res.put("requestId", UUID.randomUUID().toString());
res.put("datatime", DateUtils.getDatetime());
// ReturnResult.get(res);
System.out.println("hello: " + name);
}
@PostConstruct
public void after(){
System.out.println(this.httpServletRequest);
}
}
疑虑1
作为一个外部对象,HttpServletRequest为什么可以在Spring项目中通过注入的方式获取?
由关于@PostConstruct的描述知,@PostConstruct注解被用在执行完依赖注入之后的方法调用上,我们将断点打在上述Demo的第19行,即可查看HttpServletRequest httpServletRequest实例化之后的情况。
由上图我们可以看到这个httpServletRequest对象是一个代理对象(org.springframework.web.context.support.WebApplicationContextUtils.RequestObjectFactory),该对象是一个请求的对象工厂。进入WebApplicationContextUtils这个类,Command + F12我们发现了一个往 ConfigurableListableBeanFactory工厂对象注入bean对象的方法registerWebApplicationScopes。
该方法有这样一行代码,beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
由于HttpServletRequest正是继承自ServletRequest,这里引起了我们的关注。
ConfigurableListableBeanFactory的registerResolvableDependency方法又是用什么的呢?我们继续往下看。
使用相应的自动装配值注册一个特殊的依赖类型。
这适用于被认为可自动装配但未在工厂中定义为 bean 的工厂/上下文引用
看到这里我们就明白了,原来Spring可以为一个类注入另一个完全不同的对象值,这样从IoC容器中引用这个类的时候其实拿到的就是这个对象值。且上面的描述正好回应了,为什么我们没有手动定义HttpServletRequest却可以完成它的自动装配,秘诀就在这里。(进一步拓展思考,上面两张图一起看,除了ServletRequest.class,ServletResponse.class,HttpSession.class以及WebRequest.class类型对象,均被Spring自动注入,可以直接通过注解的方式引用)。
继续进入registerResolvableDependency方法,我们发现该方法的实现是将ServletRequest.class的依赖类型作为key, RequestObjectFactory作为装配的value,放入了resolvableDependencies这个map中。
疑虑2
HttpServletRequest注入和引用我们知道了,那么针对每个独立的请求,多线程场景下,通过自动注入的方式,HttpServletRequest 是否会有线程安全的问题呢?
我们已经知道注入拿到的HttpServletRequest返回的对象其实是下面这个
org.springframework.web.context.support.WebApplicationContextUtils.RequestObjectFactory对象,那么它是怎么做到线程安全的呢?
我们看它的具体实现。
沿着currentRequestAttributes()方法一路点击,最终发现它的返回值,是来自org.springframework.web.context.request.RequestContextHolder#requestAttributesHolder。
requestAttributesHolder对象是一个ThreadLocal对象,由此我们明白了,Spring自动注入的HttpServletRequest能够保证请求的唯一性,原来是通过跟每个线程绑定了,从ThreadLocal中取得请求信息,由此保证的线程安全!
我们再来拓展一下,既然请求信息是从ThreadLocal中取的,那么请求信息是如何放进去的呢?
我们找到requestAttributesHolder属性的set方法,在这里打一个断点,重启系统,并发起一个http请求。
最终,通过调用堆栈信息,我们看到了org.springframework.web.filter.RequestContextFilter#doFilterInternal
中调用了initContextHolder方法,而initContextHolder方法调用了requestAttributesHolder这个ThreadLocal的set方法。
每个http请求过来会进入RequestContextFilter这个filter,在这个filter中会将request信息设置到当前的线程里。到这里,所有的谜团都解开了。
总结
我们来做一个总结,在代码中通过注解注入HttpServletRequest的方式,拿到的其实并不是真正的 HttpServletRequest,而是一个Spring项目启动时自动注入的代理对象org.springframework.web.context.support.WebApplicationContextUtils.RequestObjectFactory。
该对象跟ServletRequest.class做了映射关联,放入了Spring管理bean注入的map中。
每次请求时,如何保证自动注入的HttpServelet请求线程安全,等价于问 org.springframework.web.context.support.WebApplicationContextUtils.RequestObjectFactory 怎么做到的线程安全。这个类只是一个简单的对象工厂,getObject方法最终从org.springframework.web.context.request.RequestContextHolder#requestAttributesHolder这个ThreadLocal中获取请求的信息,由此做到了请求之间的隔离。
参考:链接1,链接2