1. 程式人生 > >spring之我見--Controller註冊到DispatchServlet請求處理(上)

spring之我見--Controller註冊到DispatchServlet請求處理(上)

對應上一章 《spring之我見–從spring的啟動到ioc容器的建立》

今天我們探討一下Springmvc的工作原理,Springmvc的核心是Controller請求部分,所以我們的探討從Controller被註冊開始,到Controller如何被請求的。

1.Controller註冊前的準備工作

1.1 refresh()

上一章我們知道IOC容器是在ContextLoaderListener(ServletContextListener的實現類)下初始化的,而初始化的重要步驟是在ConfigurableApplicationContext的refresh ()方法下完成的,根據下面的註釋,refresh 方法負責載入配置檔案,比如常見的spring配置檔案applicationContext.xml

/**
 *  載入或重新整理配置的持久表示,這可能是XML檔案、屬性檔案或關係資料庫模式。
 * Load or refresh the persistent representation of the configuration,
 * which might an XML file, properties file, or relational database schema.
 * <p>As this is a startup method, it should destroy already created singletons
 * if it fails, to
avoid dangling resources. In other words, after invocation * of that method, either all or no singletons at all should be instantiated. * @throws BeansException if the bean factory could not be initialized * @throws IllegalStateException if already initialized and multiple refresh * attempts are not
supported */ void refresh() throws BeansException, IllegalStateException;

這是refresh()具體實現,做了很多事,我們具體進obtainFreshBeanFactory()方法。

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                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) {
                if (logger.isWarnEnabled()) {
                    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;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

當走到refreshBeanFactory()方法,我們看到loadBeanDefinitions()方法,官方解釋為 將bean定義載入到給定的bean工廠中,通常是通過委託給一個或多個bean定義閱讀器。話句話說 這裡就是生成BeanDefinition元資料的地方,BeanFactory不直接儲存例項,而是元資料,根據需要用反射等手段將BeanDefinition轉換成例項,我們的Controller當然也需要先轉換成元資料,

protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

spring的呼叫棧比較深,我這裡只是挑重要的地方解釋,最主要還是自己debug走一遍。
當走到loadBeanDefinitions(XmlBeanDefinitionReader reader)方法(這跟上一個loadBeanDefinitions方法不是一樣,是過載方法),我們發現它開始讀配置檔案,這個configLocations陣列就一個String物件:“/WEB-INF/applicationContext.xml” .

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            for (String configLocation : configLocations) {
                reader.loadBeanDefinitions(configLocation);
            }
        }
    }

而我們的applicationContext.xml檔案就寫了兩句,為了註冊一個controller。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- @Controller 掃描 -->
    <context:component-scan base-package="controller" />

    <!-- 註解驅動: 作用:替我們自動配置最新版的註解的處理器對映器和處理器介面卡 -->
    <mvc:annotation-driven />

</beans>

順便我也把controller貼出來吧。

package controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * @author panqian
 * @Description:
 * @date 2018/1/26
 */
@RestController
@RequestMapping("controller")
public class TestController {

    @GetMapping("test")
    public void test(HttpServletRequest request) {
        System.out.println("");
    }
}

有人問為什麼系統知道“/WEB-INF/applicationContext.xml” 這個路徑,一開始我也納悶,但是既然能debug,我們就能知道程式執行的堆疊鏈,所以我在setConfigLocation打一個斷點,根據堆疊鏈往前找,直接就可以找到答案。
這裡寫圖片描述

sc就是ServletContext,還記得上一章我們在web.xml寫的一個啟動的InitParameter?路徑就是從這裡取的。

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
    wac.setConfigLocation(configLocationParam);
}

1.2XmlBeanDefinitionReader負責配置檔案解析

讓我們繼續往下看,剛剛拿到了檔案路徑,那我們的解析就交給了XmlBeanDefinitionReader負責,這裡面涉及解析節點,提取資訊,又是很深的呼叫棧,主要介紹一下MvcNamespaceHandler ,它針對不同的配置提供多種解析器,比如annotation-driven就用AnnotationDrivenBeanDefinitionParser。

package org.springframework.web.servlet.config;

import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 * {@link NamespaceHandler} for Spring MVC configuration namespace.
 *
 * @author Keith Donald
 * @author Jeremy Grelle
 * @author Sebastien Deleuze
 * @since 3.0
 */
public class MvcNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
        registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
        registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
        registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
        registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
    }

}

AnnotationDrivenBeanDefinitionParser又具體做了什麼事呢?看它的parse()方法,

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        Object source = parserContext.extractSource(element);
        XmlReaderContext readerContext = parserContext.getReaderContext();

        CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
        parserContext.pushContainingComponent(compDefinition);

        RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
// 生成 RequestMappingHandlerMapping,用於處理@Controller和@RequestMapping註解的  
        RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
        handlerMappingDef.setSource(source);
        handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        handlerMappingDef.getPropertyValues().add("order", 0);
        handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);

        if (element.hasAttribute("enable-matrix-variables")) {
            Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
            handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
        }
        else if (element.hasAttribute("enableMatrixVariables")) {
            Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables"));
            handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
        }

        configurePathMatchingProperties(handlerMappingDef, element, parserContext);

// 將該BeanDefinition加入BeanFactory工廠(ApplicationContext)
        readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);

。。。。。。
    }

上面註冊的RequestMappingHandlerMapping 和RequestMappingHandlerAdapter是為後面controller註冊和使用準備的,只有配置了<mvc:annotation-driven /> 才會註冊這些元件。

說了<mvc:annotation-driven /> 是不是還有一個<context:component-scan base-package="controller" /> ?這個標籤也有自己的parse–ComponentScanBeanDefinitionParser,它的parse方法更加簡單。

basePackage 拿到引數是我配的 “controller”,所以scanner.doScan方法直接到該包下拿到了testController的beanDefinitions,隨後註冊到 ApplicationContext中。

@Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
        basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
        String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

        // Actually scan for bean definitions and register them.
        ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
        Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
        registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

        return null;
    }

2.生火燒飯! Controller註冊

通過前面的分析,我們已經有了用於 Controller註冊的RequestMappingHandlerMapping,TestController也被註冊進了IOC容器中,萬事俱備,我們重新返回refresh()方法中,剛剛都是走的obtainFreshBeanFactory();而接下來的finishBeanFactoryInitialization()很重要,首先這個方法用於例項化所有(非懶載入)單例物件。 然後RequestMappingHandlerMapping是單例的,非懶載入的,所以RequestMappingHandlerMapping也會在這個方法裡處理,再加上 RequestMappingHandlerMapping的afterPropertiesSet方法是具體註冊Controller的地方,所以我們需要重點跟蹤finishBeanFactoryInitialization()的程式碼。

這是我根據的doGetBean()的部分程式碼,當輪到RequestMappingHandlerMapping開始建立例項時,會走複寫的getObject()方法。

    protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {
// Create bean instance.
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                        @Override
                        public Object getObject() throws BeansException {
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            catch (BeansException ex) {
                                // Explicitly remove instance from singleton cache: It might have been put there
                                // eagerly by the creation process, to allow for circular reference resolution.
                                // Also remove any beans that received a temporary reference to the bean.
                                destroySingleton(beanName);
                                throw ex;
                            }
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }

}

繼續往裡走,populateBean是在封裝例項欄位值,繼續進initializeBean方法

// Initialize the bean instance.
        Object exposedObject = bean;
        try {
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
        }

重點關注invokeInitMethods方法,這個方法給例項機會去 做一些初始化操作,而這個機會就是複寫afterPropertiesSet()方法。

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
。。。。
try {
            invokeInitMethods(beanName, wrappedBean, mbd);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
        }
。。。。
}

invokeInitMethods方法會呼叫afterPropertiesSet(),那RequestMappingHandlerMapping怎麼複寫了這個方法呢?

((InitializingBean) bean).afterPropertiesSet();
@Override
    public void afterPropertiesSet() {
        this.config = new RequestMappingInfo.BuilderConfiguration();
        this.config.setUrlPathHelper(getUrlPathHelper());
        this.config.setPathMatcher(getPathMatcher());
        this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
        this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
        this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
        this.config.setContentNegotiationManager(getContentNegotiationManager());

        super.afterPropertiesSet();
    }
@Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }

這裡真相大白,它會先通過isHandler(beanType)判斷是不是有@Controller或@RequestMapping註釋,是的話進入detectHandlerMethods(),最後將對映寫入mappingRegistry,這是一個MappingRegistry型別,所有的前端對映都會記錄在這裡,也就達到了註冊controller的目的。

    /**
     * Scan beans in the ApplicationContext, detect and register handler methods.
     * @see #isHandler(Class)
     * @see #getMappingForMethod(Method, Class)
     * @see #handlerMethodsInitialized(Map)
     */
    protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    beanType = getApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                if (beanType != null && isHandler(beanType)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

3.參考文獻

https://www.cnblogs.com/kindevil-zx/p/6603154.html
http://blog.csdn.net/lovesomnus/article/details/49801593