主題
在使用spring框架開發(fā)的時(shí)候立帖,我們經(jīng)常會碰到這種情況:
@Controller
public class SomeController {
@RequestMapping("/test1")
public String test1(HttpServletRequest request) {
System.out.println(request.getQueryString());
return "";
}
@RequestMapping("/test2")
public String test2(HttpServletRequest request) {
System.out.println(request.getQueryString());
return "";
}
@RequestMapping("/test3")
public String test3(HttpServletRequest request) {
System.out.println(request.getQueryString());
return "";
}
}
即龙宏,一個(gè)@Controller或@Service中的多個(gè)方法都使用到了request這個(gè)參數(shù),那么為了簡化代碼,我們會將request作為成員變量注入重斑,改寫成如下形式:
@Controller
public class SomeController {
@Resource
private HttpServletRequest request;
@RequestMapping("/test1")
public String test1() {
System.out.println(request.getQueryString());
return "";
}
@RequestMapping("/test2")
public String test2() {
System.out.println(request.getQueryString());
return "";
}
@RequestMapping("/test3")
public String test3() {
System.out.println(request.getQueryString());
return "";
}
}
這樣,我們就可以不必在每個(gè)用到request的方法的參數(shù)列表都寫一遍HttpServletRequest request這個(gè)參數(shù)了肯骇。工作中也經(jīng)常是這樣做的窥浪。
思考
但是,仔細(xì)思考一下笛丙,我們會有這樣的疑問:
- spring中@Controller默認(rèn)是單例的漾脂,其成員變量也是在bean初始化時(shí)注入好的,而HttpServletRequest這個(gè)變量對于每一次請求都是不同的胚鸯,難道@Controller對每次請求都重新注入request這個(gè)成員變量骨稿?且不說這種做法不符合spring對bean管理的一般方式,即使這樣做了姜钳,線程安全如何保證坦冠?
實(shí)現(xiàn)
先上結(jié)論,上面的寫法哥桥,每次都可以取到正確的request對象蓝牲,并且是線程安全的。
那么泰讽,spring是如何做到的呢例衍?
首先debug看下兩種做法真實(shí)注入類的區(qū)別:
區(qū)別很明顯,
作為成員變量注入的時(shí)候注入的是代理對象已卸,是 AutowireUtils.ObjectFactoryDelegatingInvocationHandler的實(shí)例佛玄。
而作為方法參數(shù)注入的就是我們一般使用的Request對象。
看下ObjectFactoryDelegatingInvocationHandler這個(gè)AutowireUtils的內(nèi)部類:
/**
* Reflective 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();
}
}
}
可以看到累澡,當(dāng)代理對象的方法被調(diào)用時(shí)梦抢,除去少數(shù)幾個(gè)方法,大部分的情況都是通過this.objectFactory.getObject()獲取被代理對象愧哟,再調(diào)用被代理對象的相應(yīng)方法
通過一步步debug奥吩,最終終于看到了熟悉的Request類哼蛆,可以看到它是從requestAttributesHolder中取到的,那么requestAttributesHolder又是什么呢霞赫?
看到這里答案已經(jīng)很明了了腮介,這個(gè)RequestContextHolder的ThreadLocal成員變量就是實(shí)現(xiàn)的關(guān)鍵所在,它存放了每個(gè)線程對應(yīng)的Request對象端衰,因此在@Controller中調(diào)用作為成員變量注入的代理類的方法時(shí)叠洗,最終可以取到當(dāng)前線程相對應(yīng)的Request對象,并調(diào)用Request對應(yīng)的方法旅东,這樣@Controller中的成員變量不需要重復(fù)注入(它一直都是最初bean初始化時(shí)注入的代理類)灭抑,也避免了線程不安全的問題。
那么spring是何時(shí)將Request放入這個(gè)ThreadLocal之中的呢抵代?
是在Springmvc的dispatcherServlet的父類FrameworkServlet里操作的:
可以看到對Servlet的doGet腾节、dePost等各種調(diào)用,最終都通過processRequest(request, response)處理
該方法調(diào)用了initContextHolders(request, localeContext, requestAttributes);
最終看到了將Request放入ThreadLocal的操作荤牍。
總結(jié)
- 在bean中注入作為成員變量的HttpServletRequest時(shí)禀倔,實(shí)際注入的是spring框架生成的代理對象,是ObjectFactoryDelegatingInvocationHandler的實(shí)例参淫。在我們調(diào)用這個(gè)成員變量的方法時(shí)救湖,最終是調(diào)用了objectFactory的getObject()對象的對應(yīng)方法,在這里objectFactory是RequestObjectFactory這個(gè)類的對象涎才。
- RequestObjectFactory的getObject方法是從RequestContextHolder的threadlocal中去取值的鞋既。
- 請求剛進(jìn)入springmvc的dispatcherServlet的時(shí)候會把request相關(guān)對象設(shè)置到RequestContextHolder的threadlocal中去.