对于一个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中。