SpringMVC的Controller中注入Servlet

我们知道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混淆了。

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值