SpringMvc學習心得(四)springmvc中request的線程安全問題

對於一個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中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章