對於一個Javaweb項目,在沒有特殊配置的情況下,該項目中每個servlet是單例的。但是出於性能考慮,servlet容器必須以多線程的方式去處理http請求。這種多線程與單例之間的矛盾必然會引出一個問題,那就是線程安全。爲了保證線程安全,在servlet對象當中不建議使用成員變量,在操作成員變量時也建議加上synchronize關鍵字。
同樣的矛盾也存在與spring當中,一個被@Controller註解修飾的類對於beanfactory是單例的,不過奇怪的是我們可以使用@Autowired註解爲一個Controller類配置request,response等成員變量。很顯然,request和response必然會存在於多個線程中,而Controller類是單例的,這種做法似乎也存在線程安全問題。但是spring依舊允許這種配置方式的存在,原因是什麼?
首先說一下結論,spring中使用@Autowired註解爲一個類配置request,response等成員變量是沒有問題的。spring基於以下四個技術和具體事實成功避免了線程安全問題
1.在Tomcat和Jetty中,request和response對象對於線程單例的;
2.基於Invocationhandler的動態代理;
3.ThreadLoacl是從當前線程中獲取對象;
4.在web項目中,可以使用Listener監聽並處理http請求;
首先解釋一下1
爲了提高處理效率,Tomcat和Jetty均使用react模式多線程的處理http請求。而一個處理線程會包含一個request和response對象。當請求發起時,request對象裝載請求參數,當請求返回時,response輸出緩衝區的數據,request則清空其加載的參數,然後等待處理下一次請求。換句話說,只要這個處理線程存在,這兩個對象永遠爲null,更不會被垃圾回收機制處理。servlet規範中,要求每個request和response對象只能在servlet的service方法中有效,但是同時也允許容器重複使用這些對象。
一句話總結:request對象和response對象在一個處理線程中是唯一的;
然後解釋一下2
spring在注入使用一個統一的代理對象去處理bean的注入問題,該對象就是ObjectFactoryDelegatingInvocationHandler,該類是AutowireUtils的一個私有類,該類攔截了除了equals、hashcode以及toString以外的其他方法。具體代碼如下,其中的objectFactory是RequestObjectFactory實例
private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable { private final ObjectFactory objectFactory; public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) { this.objectFactory = objectFactory; } 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(); } } }只有某些特殊的纔可以使用上面的代理模式,對於request對象,該映射關係被配置在WebApplicationContextUtils中
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);
}
}
換句話說,對於request對象而言,ObjectFactoryDelegatingInvocationHandler中的objectFactory對象是一個RequestObjectHactory實例。
一句話總結:在使用@Autowired注入request時,實際注入的是ServletRequest的代理對象;
解釋一下3
由於request對於線程是單例的,那麼作爲代理對象獲取request的工廠類,RequestObjectHactory的主要作用就是從當前線程中獲取request對象。事實上spring也是這麼做的
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
public ServletRequest getObject() {
return currentRequestAttributes().getRequest();
}
@Override
public String toString() {
return "Current HttpServletRequest";
}
}
而currentRequestAttributes()則通過多層嵌套,從當前的ThreadLoacl中獲取到request對象,下面的代碼片段解釋了currentRequestAttributes()的實際調用requestAttributesHolder是一個ThreadLocal對象public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
一句話總結:在調用@Autowired注入request的方法時,代理對象從當前線程中獲取真正request對象,並放回該對象的方法調用結果;
最後解釋一下4
既然request對象是從當前線程獲取的,那request又在什麼時候被設置到線程中的呢?在springmvc中我們使用的是DispatcherServlet,該servlet繼承自FrameworkServlet,該servlet在處理request請求前會嘗試從threadLocal中獲取request,如果無法獲取則將request設置到Threadlocal中,具體代碼如下
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// Expose current LocaleResolver and request as LocaleContext.
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
// Expose current RequestAttributes to current thread.
RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = null;
if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
// Clear request attributes and reset thread-bound context.
LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
requestAttributes.requestCompleted();
}
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
this.logger.debug("Successfully completed request");
}
}
if (this.publishEvents) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause));
}
}
}
由於web容器在啓動時會初始換一些數量的線程來處理http請求,spring使用RequestContextListener在servletContext初始化時對線程的ThreadLoacl配置request對象public void requestInitialized(ServletRequestEvent requestEvent) {
if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
throw new IllegalArgumentException(
"Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
}
HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
LocaleContextHolder.setLocale(request.getLocale());
RequestContextHolder.setRequestAttributes(attributes);
}
一句話總結:RequestContextListener在servletContext初始化時爲ThreadLoacl配置request對象,FrameworkServlet作爲兜底策略,保證所有被DispatcherServlet處理的request在被處理前都被設置到ThreadLocal中;
總的來說。spring能夠成功得解決request對象的線程安全問題。該問題解決的基礎是request對象在線程中單例。然後注入代理對象,該代理對象能夠從當前線程的ThreadLocal中獲取request對象,並獲取方法的執行結果。最後,spring通過Listener和Servlet保證在請求處理前request對象被設置到線程的ThreadLocal中。