spring如何注入作用域不同的bean源碼分析(以HttpServletRequest爲例)

spring如何注入作用域不同的bean源碼分析(以HttpServletRequest爲例)

1.現象

衆所周知,在spring的bean中使用HttpServletRequest可以使用

@Autowired
protected HttpServletRequest request;

或者在controller的方法入參中加入HttpServletRequest

public void exportOrder(HttpServletRequest request) {
    return null;
}

但是爲什麼可以這樣使用?controller類默認是單例的,使用@Autowired注入的response和request卻可以在多個線程中使用,這就出現了作用域request級別的bean注入到singleton級別的bean中去的現象。

那麼spring是如何保證在singleton作用域中使用更長作用域的bean的呢?

2.spring如何注入作用域不同的bean

spring如何注入作用域不同的bean

在此文中說明了spring中,如果需要將作用域較大的bean A注入較小的bean B中時,其實注入的是A的代理對象,在需要時使用代理getObject()來獲取一些指定的依賴。

3.HttpServletRequest如何初始化

在controller中使用HttpServletRequest時,我們並不需要注入就可以使用。這是因爲spring初始化的過程中,就已經把HttpServletRequest註冊到容器中去了。通過spring的refresh()方法的postProcessBeanFactory(beanFactory)
image-20200423162921932
該方法實現的ServletWebServerApplicationContextpostProcessBeanFactory調用了WebApplicationContextUtils.registerWebApplicationScopes()
image-20200423162305829### 4.HttpServletRequest的使用
那麼知道了是如何注入之後,那麼又是如何使用的呢。使用一個例子來看一看:

/**
 * @author LWong
 * @date 2020/04/23
 */
@RestController("req")
public class ExportController {

    @Autowired
    private HttpServletRequest httpServletRequest;

    @PostMapping("export")
    public void export(HttpServletRequest httpServletRequest1){
        String method = httpServletRequest.getMethod();
        System.out.println(method);
    }
}

分別在方法入參和controller中引用HttpServletRequest,打斷點查看httpServletRequesthttpServletRequest1
image-20200423165324253
在圖片中可以發現httpServletRequest是一個代理對象是,作爲成員變量注入的時候注入的是代理對象AutowireUtils.ObjectFactoryDelegatingInvocationHandler的實例,而作爲方法參數注入的就是我們一般使用的Request對象。那麼來看看這個handler

/**
	 * Reflective {@link InvocationHandler} for lazy access to the current target object.
	 */
	@SuppressWarnings("serial")
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 		  {
			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();
			}
		}
}

通過this.objectFactory.getObject()方法進入到WebApplicationContextUtils.currentRequestAttributes()
image-20200423171505593
從這個又要進入RequestContextHolder的getRequestAttributes方法
image-20200423171649711
而requestAttributesHolder正是初始化的threadLocal,保證了線程安全
image-20200423171732772

看到這裏答案已經很明瞭了,這個RequestContextHolderThreadLocal成員變量就是實現的關鍵所在,它存放了每個線程對應的Request對象,因此在@Controller中調用作爲成員變量注入的代理類的方法時,最終可以取到當前線程相對應的Request對象,並調用Request對應的方法,這樣@Controller中的成員變量不需要重複注入(它一直都是最初bean初始化時注入的代理類),也避免了線程不安全的問題。


5.總結

  1. 在bean中注入作爲成員變量的HttpServletRequest時,實際注入的是spring框架生成的代理對象,是ObjectFactoryDelegatingInvocationHandler的實例。在我們調用這個成員變量的方法時,最終是調用了objectFactorygetObject()對象的對應方法,在這裏objectFactoryRequestObjectFactory這個類的對象。
  2. RequestObjectFactorygetObject方法是從RequestContextHolder的threadlocal中去取值的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章