当前位置: 代码迷 >> 综合 >> 【Spring】spring如何注入ServletRequest、ServletResponse
  详细解决方案

【Spring】spring如何注入ServletRequest、ServletResponse

热度:76   发布时间:2024-02-21 12:39:54.0

使用姿势 

   spring里有很多小知识点,今天学习一下ServletRequest、ServletResponse是如何注入容器里面的。

    看一个小例子,如果我们想在controller的方法里面获取request可以怎么做?

  • 参数中添加对应参数

    这种方式就是在方法后面加一个HttpServletRequest或者HttpServletResponse参数,Sping做参数绑定的时候特殊处理。这个不是今天的主角。。。

@RequestMapping("hello3")public String hello(HttpServletRequest request3) {System.out.println(request3.getRequestURI());return "hello world!";}
  • bean注入

    在对应controller中添加@Resource或者@Autowire注解即可。今天主要记录学习bean注入方式的机制原理。

@Resourceprivate HttpServletRequest request;@RequestMapping("hello")public String hello() {System.out.println(request.getRequestURI());return "hello world!";}

原理分析

    bean注入方式的机制是怎么样的?为啥直接添加这样的依赖就可以实现ServletRequest实例注入?又是如何做到每个请求的Request不一样的?我们以HttpServletRequest为例(HttpServletResponse机制是一样的)。

   首先,要使@Resource或@Autowire能正常工作,Bean容器中一定有对应的类型的bean。这一点是毋庸置疑的。所以首先我们要找到如何注入的。走读容器启动的代码发现:org.springframework.web.context.support.AbstractRefreshableWebApplicationContext#postProcessBeanFactory 在这个方法中,调用了方法org.springframework.web.context.support.WebApplicationContextUtils#registerWebApplicationScopes(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, javax.servlet.ServletContext)。我们看一下它的庐山面目:

public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) {beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));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);}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);}}

这个方法先是注册了3个自定义的scope,然后注入了4个bean,其中包括了RequestObjectFactory。继续看这个类,RequestObjectFactory是一个ObjectFactory实现类,getObject返回调用currentRequestAttributes方法,并从currentRequestAttributes返回的对象ServeletRequestAttributes中取到Request对象

public abstract class WebApplicationContextUtils {......
@SuppressWarnings("serial")private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {@Overridepublic ServletRequest getObject() {return currentRequestAttributes().getRequest();}@Overridepublic String toString() {return "Current HttpServletRequest";}}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;}......
}

currentRequestAttributes方法又调用了org.springframework.web.context.request.RequestContextHolder#getRequestAttributes方法。在方法getRequestAttributes中从线程变量(ThreadLocal)requestAttributesHolder获取RequestAttributes(servlet容器实际类型是ServletRequestAttributes)类型的对象。

public static RequestAttributes currentRequestAttributes() throws IllegalStateException {RequestAttributes attributes = getRequestAttributes();if (attributes == null) {... ...}return attributes;}public static RequestAttributes getRequestAttributes() {RequestAttributes attributes = requestAttributesHolder.get();if (attributes == null) {attributes = inheritableRequestAttributesHolder.get();}return attributes;}

   到这里,我们知道了ServletRequest是什么时候注册到容器中的,容器在启动时注册了RequstObjectFactory的实例。

   如果只是这样注入的,那么在系统起来,controller实例化好以后request就创建了,而且那个时候还没有请求进来,线程变量requestAttributesHolder应该是null。即便不是null, 线程变量对象是ServletRequestAttributes,ServletRequestAttributes对象 的request变量也是null。这里spring又很巧妙,在controller中注入的HttpServletRequest对象不是从RequstObjectFactory#getObject取到的,而是生成一个代理类对象并赋给controller中的属性。 spring在解析依赖实例的时候调用方法org.springframework.beans.factory.support.AutowireUtils#resolveAutowiringValue。如果依赖实例是一个ObjectFactory且期望的类型是接口的话,就会创建一个代理类。代理类有个重要角色InvocationHandler(去了解动态代理),这里传的是ObjectFactoryDelegatingInvocationHandler。

public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;if (autowiringValue instanceof Serializable && requiredType.isInterface()) {autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));}else {return factory.getObject();}}return autowiringValue;}

 ObjectFactoryDelegatingInvocationHandler将所有调用代理类对象的方法传递到RequestObjectFactory.getObject()对象上,上面分析了RequestObjectFactory#getObject就是获取线程变量中的真实Request。

private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {private final ObjectFactory<?> objectFactory;public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {this.objectFactory = objectFactory;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String methodName = method.getName();if (methodName.equals("equals")) {// Only consider equal when proxies are identical.return (proxy == args[0]);}else if (methodName.equals("hashCode")) {// Use hashCode of proxy.return System.identityHashCode(proxy);}else if (methodName.equals("toString")) {return this.objectFactory.toString();}try {return method.invoke(this.objectFactory.getObject(), args);}catch (InvocationTargetException ex) {throw ex.getTargetException();}}}

   至此,我们明白了,spring注册的bean是一个ObjectFactory,依赖注入的时候设置的一个代理对象。所有调用代理对象的方法都通过ObjectFactoryDelegatingInvocationHandler传递到ObjectFactory.getObject的对象上。而RequstObjectFactory从线程变量中获取真实Request。

    那么,还剩一个问题,那就是线程变量什么时候设置的。答案在这里:org.springframework.web.servlet.FrameworkServlet#processRequest。请求都会经过这个方法(servlet规范-springmvc只是servlet容器中其中一个servlet而已)。这个方法调用了org.springframework.web.servlet.FrameworkServlet#initContextHolders。

private void initContextHolders(HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {if (localeContext != null) {LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);}if (requestAttributes != null) {RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);}if (logger.isTraceEnabled()) {logger.trace("Bound request context to thread: " + request);}}
  • 总结

  • 1 首先注册bean,spring的bean容器启动时注册一个RequestObjectFactory对象,RequestObjectFactory是ObjectFactory,RequestObjectFactory#getObject兜兜转转从线程变量中获取Request对象;
  • 2 其次给依赖HttpServletRequest地方注入bean,spring注入的bean是一个代理对象,代理对象的InvocationHandler是ObjectFactoryDelegatingInvocationHandler,它将请求都转发到RequestObjectFactory#getObject的对应方法上;
  • 3 最后http请求在进入到系统中时都会经过org.springframework.web.servlet.FrameworkServlet#processRequest,这个方法会往线程变量中设置ServletRequestAttributes对象(含有一个属性变量Request)。
  相关解决方案