我们知道Servlet使用方式是单实例多线程的,Controller在不使用@Scope("prototype")注解时也是单实例的,下面的使用方式:
@Controller
public class BlogController {
@Autowired
private HttpServletRequest request;
}
初看之下,因为BlogController中有Servlet数据成员,那这个Controller肯定是有状态的,在多线程环境下,有状态的单实例势必导致线程安全问题,但是事实是这种写法是线程安全的。所以我们需要了解SpringMVC是如何处理@Controller注解的,有几点疑问是大家应该意识到的,就是HttpServletRequest类你是没有加入到Spring IOC容器的,那Spring是如何能将其注入到已经在IOC容器中的Controller中的呢?其次是Controller是单实例的,其中数据成员request就会具有状态,线程安全问题是如何解决的?我们先声明,这种写法是不存在线程安全问题的。
下面就直接来说说其中的原理,我们知道SpringMVC中所有请求都是由DispatcherServlet这个类型来接管的,通过HandlerMapping获取到HandlerExecutionChain,然后根据HandlerExecutionChain获取到对应的HandlerAdapter,HandlerAdapter调用handle()方法,执行业务处理,得到ModeAndView返回值,DispatcherServlet根据ModeAndView响应请求。那Controller中成员变量的注入实在什么时候进行的?
我们知道,每个请求都会经手DispatcherServlet,所以每次请求来的时候调用的那个方法里面肯定会有赋值操作,下面是FrameworkServlet中service()方法,这个方法中重要的就是processRequest(),参数是类型是HttpServletRequest,和HttpServletResponse
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
接下来我们看看processRequest()方法是如何处理Servlet的,其中LocaleContext是国际化相关设置,我们这里忽略,所以我们需要关注的是initContextHolders()方法和resetContextHolders(),可以看到Servlet相关的参数都被封装到ServletRequestAttributes中了,
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
下面是initContextHolders()方法,同样我们忽略国际化相关的LocaleContext,只看RequestContextHolder是如何处理的
private void initContextHolders(HttpServletRequest request,
@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
}
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
}
下面是RequestContextHolder#setRequestAttributes()方法,可以看到是将RequestAttributes放到ThreadLocal中了,至于有关InheritableThreadLocal的使用可以看《InheritableThreadLocal使用》这篇文章,
public abstract class RequestContextHolder {
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
public static void resetRequestAttributes() {
requestAttributesHolder.remove();
inheritableRequestAttributesHolder.remove();
}
public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
}
else {
if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
}
else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
}
}
到了这里,每次请求的值的重设我们大概了解了,下面需要了解下初始化的时候是如何注入这些Servlet域的,这个工作和SpringMVC初始化相关,那我们就直接进入正题,首先我们说明,注入的对象不是原生类型,而是代理类型,起点是XmlWebApplicationContext
这里我们追溯的起点是AbstractApplicationContext#refresh(),这个方法里面定义了一个比较清晰的流程,但是许多方法都是留给子类去实现的,这里我们需要关注的是refresh()方法中的postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法,在AbstractApplicationContext中这个方法是空的,所以我们需要追溯到子类中去看实现方式,这里我们看AbstractRefreshableWebApplicationContext#postProcessBeanFactory()这个方法,这里我们简单介绍下这些方法的作用
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 给指定factory添加一个BeanPostProcessor
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
// 忽略依赖检查的接口
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
// 注册上下文
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
// 注册Spring环境变量相关Bean
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}
这里我们需要关注的是registerWebApplicationScopes()这个方法, 这里我们需要理解的是registerResolvableDependency()这个方法的含义,这个方法是设定某个类型的依赖注入由指定的类型提供,这里的ServletRequest类型实例的注入就由RequestObjectFactory来提供,所以肯定不是原生类型了
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
@Nullable ServletContext sc) {
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
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);
}
// 这个factory是我们需要关注的
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);
}
}
那我们还是需要了解是何时注入的, 其实就是在程序启动时就会注入,启动时会调用依赖解析的类来给这些类数据成员进行注入,整个调用链还是比较复杂的,大家可以通过debug查看,大概的起点还是可以追溯到refresh()方法,下面展示一个调用堆栈的一部分,
我们直接看resolveAutowiringValue()这个方法,这里的入参在运行时分别是RequestObjectFactory实例和HttpServletRequest.class,这里可以看到,返回的是通过JDK Proxy生成的一个代理对象,类型是ObjectFactoryDelegatingInvocationHandler,所以我们需要看看这个子类是如何操作的
public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
}
else {
return factory.getObject();
}
}
return autowiringValue;
}
ObjectFactoryDelegatingInvocationHandler#invoke()方法,可以看到当我们引用HttpServletRequest的时候调用的是RequestObjectFactory这个类的实例
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();
}
}
所以我们还需要看看 RequestObjectFactory#getObject()方法是如何实现的,
@Override
public ServletRequest getObject() {
return currentRequestAttributes().getRequest();
}
接下来看看WebApplicationContextUtils#currentRequestAttributes()方法,这里出现了RequestContextHolder,和上面我们提到的就串起来了,
private static ServletRequestAttributes currentRequestAttributes() {
RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
if (!(requestAttr instanceof ServletRequestAttributes)) {
throw new IllegalStateException("Current request is not a servlet request");
}
return (ServletRequestAttributes) requestAttr;
}
下面我们对这个过程做个总结,程序启动时,会对HttpServletRequest进行注入,对这个变量注入的值不是原生HttpServletRequest类型,而是ObjectFactoryDelegatingInvocationHandler这个代理类型,代理方法的时候使用RequestObjectFactory#getObject()获取到的实例,getObject()方法是从RequestContextHolder中获取真正的HttpServletRequest,每次请求到了的时候,会把HttpServletReuest塞到RequestContextHolder中,所以即使Controller是单例的,里面注入的HttpServletRequest却是和请求同步更新的,不会有线程安全问题。另外,不要把HttpServlet和HttpServletRequest混淆了。