1. 程式人生 > >【轉】在SpringMVC Controller中注入Request成員域

【轉】在SpringMVC Controller中注入Request成員域

原文連結:https://www.cnblogs.com/abcwt112/p/7777258.html

原文作者:abcwt112

主題

  在工作中遇到1個問題....我們定義了一個Controller基類,所有Springmvc自定義的controller都繼承它....在它內部定義一個@Autowired HttpServletRequest request;可不可以? 能不能從這個物件裡取requestParamters和attributes? 多執行緒之間會不會影響?

 

思考

初次思考,我想這應該是不行的.為什麼呢?

注入bean是在spring容器啟動的時候...request的實現類是在tomcat裡(我使用的servlet容器是tomcat)....我又沒在spring的容器裡配置這個bean.注入應該是失敗的...

退一步說,就算是成功了....那注入的也就是1個物件而已.每次servlet接受到請求都會重新生成1個request...這明顯和之前啟動的那個物件不同吧....怎麼想都不可能成功...

 

如果確實是這樣的....那就沒有這篇文章了....後來實踐了一下..發現這個注入是可以的.使用起來取資料也沒任何問題....

 

其實我那個時候debug看了一下,基本就知道為什麼可以取到資料了..但是我並不知道原理和Spring(Springmvc)的處理流程...所以現在研究了一下並記錄下來...

 

原理

首先給大家看一下在方法中注入request作為引數和在成員域中注入request的 注入的request物件之間的區別....

 

 

成員域注入的時候注入的是1個代理物件.是 AutowireUtils.ObjectFactoryDelegatingInvocationHandler的例項.

方法注入的就是一般tomcat原生的requestFacade物件.

所以這是不同的...

/**
     * Reflective InvocationHandler for lazy access to the current target object.
     */
    @SuppressWarnings("serial")
    private static
class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable { private final ObjectFactory<?> objectFactory; public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) { this.objectFactory = objectFactory; } @Override 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)的大部分方法被呼叫的時候,ObjectFactoryDelegatingInvocationHandler會使用objectFactory獲取物件(原生request),再呼叫物件上的方法.

 

 

然後我們來看下XmlWebApplicationContext初始化到請求到進入controller裡幾個對注入request成員域有影響的步驟.

 

refresh方法和postProcessBeanFactory方法

ApplicationContext的抽象實現類AbstractApplicationContext(基本是所有ac的父類)裡定義了ac的refresh方法(包含了使用BeanFactory注入bean)的流程..

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            // 記錄開始wac開始初始化的時間,設定啟用標記,servlet的相關param設定到env(之前做過1次),校驗env中必須的props
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            // 將舊的BF裡的bean刪掉,新建1個BF,設定部分屬性,載入XML配置檔案
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            // 1.設定BF解析bean配置需要用到的一些物件比如env. 2.註冊一些BeanPostProcessor比如ApplicationContextAwareProcessor去設定Aware需要的物件
            // 3.忽略一些特定class注入的物件,設定一些特定class注入的物件為指定值
            // 4.將一些env中的properties map當做bean註冊到BF中
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                // 1.設定一個BeanPostProcess為ServletContextAware的實現類注入servlet相關物件
                // 2.在BF中增加requetsScope等Scope
                // 3.把servletContext,Config,ServletInitParams,ServletAttribute當做Bean註冊到BF中
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                // 初始化並呼叫BeanFactoryPostProcessor
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                // 註冊BeanPostProcessors並註冊到BF中去
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            } catch (BeansException ex) {
                logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }
        }
    }

 

 

其中有1個模板方法

postProcessBeanFactory(beanFactory);

這個方法允許AbstractApplicationContext的子類覆蓋它並實現對BF的定製(這個時候bean的defination路徑已經指定了,但是bean還沒載入).

 

AbstractRefreshableWebApplicationContext覆蓋了這個方法

/**
     * Register request/session scopes, a {@link ServletContextAwareProcessor}, etc.
     * 1.設定一個BeanPostProcess為ServletContextAware的實現類注入servlet相關物件
     * 2.在BF中增加requetsScope等Scope
     * 3.把servletContext,Config,ServletInitParams,ServletAttribute當做Bean註冊到BF中
     *
     */
    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // 設定一個BeanPostProcess為ServletContextAware的實現類注入servlet相關物件
        beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
        beanFactory.ignoreDependencyInterface(ServletContextAware.class);
        beanFactory.ignoreDependencyInterface(ServletConfigAware.class);

        // 在BF中增加requetsScope等Scope
        WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
        // 把servletContext,Config,ServletInitParams,ServletAttribute當做Bean註冊到BF中
        WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
    }

 

其中有一步

WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);

這裡設定了一些特殊的bean的scope,比如request,session,globalSession,application.(當然這個不是我這篇文章的主題.)

同時設定了一些特殊的autowired bean

beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());

 

ServletRequest的實現類(比如HttpServletRequest)被指定使用RequestObjectFactory注入.

 

RequestObjectFactory

RequestObjectFactory就是1個ObjectFactory就是前面ObjectFactoryDelegatingInvocationHandler裡的ObjectFactory.所以在成員域request物件上呼叫方法其實就是通過RequestObjectFactory獲取物件再呼叫方法.

    /**
     * Factory that exposes the current request object on demand.
     */
    @SuppressWarnings("serial")
    private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {

        @Override
        public ServletRequest getObject() {
            return currentRequestAttributes().getRequest();
        }

        @Override
        public String toString() {
            return "Current HttpServletRequest";
        }
    }
    /**
     * Return the current RequestAttributes instance as ServletRequestAttributes.
     * @see RequestContextHolder#currentRequestAttributes()
     */
    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;
    }

 

 

RequestObjectFactory的getObject方法很簡單,就是呼叫靜態方法

RequestContextHolder.currentRequestAttributes().getRequest()

 

RequestContextHolder

 

    public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
        RequestAttributes attributes = getRequestAttributes();
        if (attributes == null) {
            if (jsfPresent) {
                attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
            }
            if (attributes == null) {
                throw new IllegalStateException("No thread-bound request found: " +
                        "Are you referring to request attributes outside of an actual web request, " +
                        "or processing a request outside of the originally receiving thread? " +
                        "If you are actually operating within a web request and still receive this message, " +
                        "your code is probably running outside of DispatcherServlet/DispatcherPortlet: " +
                        "In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
            }
        }
        return attributes;
    }
    /**
     * Return the RequestAttributes currently bound to the thread.
     * @return the RequestAttributes currently bound to the thread,
     * or {@code null} if none bound
     */
    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = requestAttributesHolder.get();
        if (attributes == null) {
            attributes = inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<RequestAttributes>("Request attributes");

    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
            new NamedInheritableThreadLocal<RequestAttributes>("Request context");

 

上面是一些關鍵方法

所以最終其實request是從threadlocal中取...

 

FrameworkServlet

 那麼request是什麼時候設定到threadlocal中去的呢?

是在Springmvc的dispatcherServlet的父類FrameworkServlet裡操作的.

    @Override
    protected final void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        processRequest(request, response);
    }


    @Override
    protected final void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        processRequest(request, response);
    }

 

不管你是doGet還是doPost還是doXXX方法都是委託processRequest方法去做的.

    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 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 {
            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(request, localeContext, requestAttributes);

 

    private void initContextHolders(
            HttpServletRequest request, LocaleContext localeContext, 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的threadlocal中去的...

 

 

小結

1.在controller中注入的request是jdk動態代理物件,ObjectFactoryDelegatingInvocationHandler的例項.當我們呼叫成員域request的方法的時候其實是呼叫了objectFactory的getObject()物件的相關方法.這裡的objectFactory是RequestObjectFactory.

2.RequestObjectFactory的getObject其實是從RequestContextHolder的threadlocal中去取值的.

3.請求剛進入springmvc的dispatcherServlet的時候會把request相關物件設定到RequestContextHolder的threadlocal中去.