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, 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中。

你可能感兴趣的:(Spring)