美文网首页
SpringMVC/SprintBoot 是如何将 Dispat

SpringMVC/SprintBoot 是如何将 Dispat

作者: 雁过留声_泪落无痕 | 来源:发表于2023-10-17 19:01 被阅读0次

    SpringMVC

    SpringMVC 是如何将 DispatcherServlet 配置到 Tomcat 中的?

    Tomcat 启动流程

    略。启动入口 main 方法在 BootStrap 中,一系列初始化后会去解析 web.xml,通过解析 web.xml 就直接实例化 DispatcherServlet 对象了。

    web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
             version="5.0">
    
        <!-- 注册 SpringMVC 的框架 -->
        <servlet>
            <servlet-name>DispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextClass</param-name>
                <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
            </init-param>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>com.example.xxx.config.AppConfig</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>DispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
    </web-app>
    
    1. 通过反射创建 DispatcherServlet 对象
      org.apache.catalina.startup.ContextConfig#configureContext() 方法中会调用 wrapper.setServletClass(servlet.getServletClass()); 将具体的类名传递给 StandardWapper 类,然后在 StandardWapper#loadServlet() 中会执行 servlet = (Servlet) instanceManager.newInstance(servletClass); 从而生成 DispatcherServlet 对象

    2. DispatcherServlet 继承关系
      DispatcherServlet 继承 FrameworkServlet 继承 HttpServletBean 继承 HttpServlet 继承 GenericServlet 实现 Servlet 接口

    3. 给 DispatcherServlet 对象设置值
      ContextConfig#configureContext() 方法调用 wrapper.addInitParameter(entry.getKey(), entry.getValue());,从而得到相关 initParameter。最终通过调用 HttpServletBean#init() 方法,进而将 initParameter 设置到 DispatcherServlet 对象上:

    @Override
    public final void init() throws ServletException {
    
        // Set bean properties from init parameters.
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }
    
        // Let subclasses do whatever initialization they like.
        initServletBean();
    }
    
    • DispatcherServlet#setContextClass(Class<?>)
    • DispatcherServlet#setContextConfigLocation(String)

    contextClass 怎么用

    FrameworkServlet#createWebApplicationContext(ApplicationContext) 中有用到 getContextClass(),根据 contextClass 指定的值生成对应的 ApplicationContext 对象,这里是 AnnotationConfigWebApplicationContext

    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        Class<?> contextClass = getContextClass();
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException(
                    "Fatal initialization error in servlet with name '" + getServletName() +
                    "': custom WebApplicationContext class [" + contextClass.getName() +
                    "] is not of type ConfigurableWebApplicationContext");
        }
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    
        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        String configLocation = getContextConfigLocation();
        if (configLocation != null) {
            wac.setConfigLocation(configLocation);
        }
        configureAndRefreshWebApplicationContext(wac);
    
        return wac;
    }
    

    这是通过 HttpServletBean#init() 调用过来的:

    @Override
    public final void init() throws ServletException {
        // Set bean properties from init parameters.
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        ...
    
        // Let subclasses do whatever initialization they like.
        initServletBean();
    }
    

    会调到 FrameworkServlet#initServletBean() 方法中:

    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
        if (logger.isInfoEnabled()) {
            logger.info("Initializing Servlet '" + getServletName() + "'");
        }
        long startTime = System.currentTimeMillis();
    
        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException | RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            throw ex;
        }
    
        ...
    }
    

    initWebApplicationContext() 方法中会进一步调用 createWebApplicationContext() 方法:

    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
    
        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            // No context instance was injected at construction time -> see if one
            // has been registered in the servlet context. If one exists, it is assumed
            // that the parent context (if any) has already been set and that the
            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
            wac = createWebApplicationContext(rootContext);
        }
    
        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            synchronized (this.onRefreshMonitor) {
                onRefresh(wac);
            }
        }
    
        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }
    
        return wac;
    }
    

    区别于 Springboot,SpringMVC 中,走到 initWebApplicationContext() 方法时(浏览器请求一下才会触发),this.webApplicationContext 为 null:

    SpringMVC.png

    而在 Springboot 中,走到 initWebApplicationContext() 方法时,this.webApplicationContext 已经有值了:

    Springboot.png

    contextConfigLocation 怎么用

    如上面的代码 wac.setConfigLocation(configLocation);,会将 contextConfigLocation 指定的值传递给 ConfigurableWebApplicationContext 对象(这是一个接口),实际是 AnnotationConfigWebApplicationContext 对象(继承 AbstractRefreshableConfigApplicationContext 类,该类实现了 setConfigLocation(String) 方法)。
    然后在 AnnotationConfigWebApplicationContext#loadBeanDefinitions() 方法中调用了 getConfigLocations() 得到在 web.xml 中具体配置的值,并通过反射得到具体的配置对象:

    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
        AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
        ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);
    
        BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
        if (beanNameGenerator != null) {
            reader.setBeanNameGenerator(beanNameGenerator);
            scanner.setBeanNameGenerator(beanNameGenerator);
            beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
        }
    
        ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
        if (scopeMetadataResolver != null) {
            reader.setScopeMetadataResolver(scopeMetadataResolver);
            scanner.setScopeMetadataResolver(scopeMetadataResolver);
        }
    
        if (!this.componentClasses.isEmpty()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Registering component classes: [" +
                        StringUtils.collectionToCommaDelimitedString(this.componentClasses) + "]");
            }
            reader.register(ClassUtils.toClassArray(this.componentClasses));
        }
    
        if (!this.basePackages.isEmpty()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Scanning base packages: [" +
                        StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]");
            }
            scanner.scan(StringUtils.toStringArray(this.basePackages));
        }
    
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            for (String configLocation : configLocations) {
                try {
                    Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
                    if (logger.isTraceEnabled()) {
                        logger.trace("Registering [" + configLocation + "]");
                    }
                    reader.register(clazz);
                }
                catch (ClassNotFoundException ex) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Could not load class for config location [" + configLocation +
                                "] - trying package scan. " + ex);
                    }
                    int count = scanner.scan(configLocation);
                    if (count == 0 && logger.isDebugEnabled()) {
                        logger.debug("No component classes found for specified class/package [" + configLocation + "]");
                    }
                }
            }
        }
    }
    

    配置对象 com.example.xxx.config.AppConfig

    @Configuration
    @EnableWebMvc
    @ComponentScan("com.example.xxx")
    public class AppConfig {
    
        @Bean
        public XXX viewResolver() {
            return new XXX();
        }
    
    }
    

    进而,将配置类对象交给了 Spring,Spring 会进一步处理配置对象上的 ComponentScan 注解等。

    最终,DispatcherServlet 对象生成并配置完毕。Tomcat 会根据 servlet-mapping 规则将请求交给 DispatcherServlet 处理:

    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    

    SprintBoot

    SprintBoot 是如何将 DispatcherServlet 配置到 Tomcat 中的?

    通常,启动类代码都是如下所示:

    @SpringBootApplication
    public class SampleTomcatApplication {
        public static void main(String[] args) {
            SpringApplication.run(SampleTomcatApplication.class, args);
        }
    }
    

    SpringApplication.run()(静态 run 方法) 方法中会实例化 SpringApplication 对象,并调用其实例 run 方法,在 run 方法中会通过 context = createApplicationContext(); 创建 ApplicationContext 对象(这里是 ServletWebServerApplicationContext 对象),并调用其 refresh 方法,进一步创建 TomcatWebServer 对象:

    调用栈.png

    TomcatWebServer#initialize() 方法中会调用 this.tomcat.start(); ,最终会走到 ApplicationContext#addServlet() 方法中,注意,这里的 ApplicationContext 是 tomcat 的 org.apache.catalina.core.ApplicationContext,最终将 DispatcherServlet 对象交到了 tomcat 手中:

    调用栈.png

    那么,DispatcherServlet 对象是在哪儿被创建的呢?在 ServletWebServerApplicationContext#createWebServer() 方法中会调用 getWebServerFactory() 方法,进而调用到 DispatcherServletAutoConfiguration$DispatcherServletConfiguration#dispatcherServlet() 方法上,该方法创建并返回了 DispatcherServlet 对象:

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
            // 这里最终会创建 DispatcherServlet 对象并放入容器中
            ServletWebServerFactory factory = getWebServerFactory();
            createWebServer.tag("factory", factory.getClass().toString());
            // 这里调用 getWebServer 会将 DispatcherServlet 对象传递给 Tomcat
            // 这里 getSelfInitializer() 是一个回调,到时候 Tomcat 通过这个回调将 ServletContext 传回给 Spring
            this.webServer = factory.getWebServer(getSelfInitializer());
            createWebServer.end();
            getBeanFactory().registerSingleton("webServerGracefulShutdown",
                    new WebServerGracefulShutdownLifecycle(this.webServer));
            getBeanFactory().registerSingleton("webServerStartStop",
                    new WebServerStartStopLifecycle(this, this.webServer));
        }
        else if (servletContext != null) {
            try {
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }
        initPropertySources();
    }
    
    @Configuration(proxyBeanMethods = false)
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    protected static class DispatcherServletConfiguration {
    
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
            dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
            return dispatcherServlet;
        }
    
        @Bean
        @ConditionalOnBean(MultipartResolver.class)
        @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
        public MultipartResolver multipartResolver(MultipartResolver resolver) {
            // Detect if the user has created a MultipartResolver but named it incorrectly
            return resolver;
        }
    
    }
    

    补充

    ServletContext 是在哪儿创建的

    在 Tomcat 的 StandardContext#getServletContext() 方法中创建的,在 StandardContext#startInternal() 中有多次调用该方法。

    ServletContext 对象是怎么传递给 Spring 的

    从上面 的代码可以知道,在 main->run->run->run->refresh->TomcatWebServer#initialize()->Tomcat#start()->...->StandardContext#startInternal() 的过程中,会创建 ServletContext 对象,但此时这个对象还是隶属于 StandardContext 的,那它是怎么被传递给 Spring 的呢?

    StandardContext.initializers 成员包含 TomcatStarter 对象。在 StandardContext#startInternal() 方法中会用到:

    protected synchronized void startInternal() throws LifecycleException {
        ...
        // Call ServletContainerInitializers
        for (Map.Entry<ServletContainerInitializer,Set<Class<?>>> entry : initializers.entrySet()) {
            try {
                entry.getKey().onStartup(entry.getValue(), getServletContext());
            } catch (ServletException e) {
                log.error(sm.getString("standardContext.sciFail"), e);
                ok = false;
                break;
            }
        }
        ...
    }
    
    class TomcatStarter implements ServletContainerInitializer {
      ...
    }
    
    public interface ServletContainerInitializer {
        public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
    }
    

    而 TomcatStarter 实现了 ServletContainerInitializer 接口,故会走到 TomcatStarter#onStartup() 方法:

    public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
        try {
            for (ServletContextInitializer initializer : this.initializers) {
                initializer.onStartup(servletContext);
            }
        }
        catch (Exception ex) {
            this.startUpException = ex;
            // Prevent Tomcat from logging and re-throwing when we know we can
            // deal with it in the main thread, but log for information here.
            if (logger.isErrorEnabled()) {
                logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
                        + ex.getMessage());
            }
        }
    }
    

    TomcatStarter 也有一个 initializers 成员,其包含 ServletWebServerApplicationContext$lambda@4202 对象,为 ServletWebServerApplicationContext 的一个 lambda 表达式。

    这里要注意一下,这个表达式是在 createWebServer() 中传递给 WebServer 的,可以理解为一个回调:

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
            ServletWebServerFactory factory = getWebServerFactory();
            createWebServer.tag("factory", factory.getClass().toString());
            // 注册回调
            this.webServer = factory.getWebServer(getSelfInitializer());
            createWebServer.end();
            getBeanFactory().registerSingleton("webServerGracefulShutdown",
                    new WebServerGracefulShutdownLifecycle(this.webServer));
            getBeanFactory().registerSingleton("webServerStartStop",
                    new WebServerStartStopLifecycle(this, this.webServer));
        }
        else if (servletContext != null) {
            try {
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context", ex);
            }
        }
        initPropertySources();
    }
    
    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
        return this::selfInitialize;
    }
    
    private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareWebApplicationContext(servletContext);
        registerApplicationScope(servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
    

    getWebServer(ServletContextInitializer) 方法的参数是 ServletContextInitializer 接口,该接口只有一个 onStartup 方法,故可以把 void selfInitialize(ServletContext) 方法传递过去(lambda 表达式的用法):

    public interface ServletContextInitializer {
        void onStartup(ServletContext servletContext) throws ServletException;
    }
    

    因此,在 TomcatStarter#onStartup() 会调到 ServletWebServerApplicationContext#selfInitialize(ServletContext) 中来,也就把 ServletContext 对象给传递过来了。

    简单来说,就是 Spring 创建 Tomcat 的 WebServer 时传递了一个一个回调过去,等 Tomcat 创建好 ServletContext 对象后再通过这个回调将 ServletContext 对象传过来。

    Springboot 中,DispatcherServlet 对象注入到了哪儿

    上面提到了 DispatcherServlet 最终通过 addServlet 交到了 Tomcat 手中,也提到了是在 DispatcherServletConfiguration 类中以 @Bean 的方式添加到了容器中,那么总会用一个地方注入了 DispatcherServlet 对象吧?

    上面提到,Tomcat 通过回调到 ServletWebServerApplicationContext#selfInitialize(ServletContext) 把 ServletContext 对象传递回到 Spring 中,下面重点看 selfInitialize() 方法中的 for 循环部分:

    private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareWebApplicationContext(servletContext);
        registerApplicationScope(servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
    

    调用 getServletContextInitializerBeans() 返回了如下相关 Bean:

    getServletContextInitializerBeans.png

    可以看到,第一个就是 dispatcherServlet urls=[/],类名为 DispatcherServletRegistrationBean,继承自 RegistrationBean,而 RegistrationBean 又实现了 ServletContextInitializer 接口,故会走到 RegistrationBean#onStartup 方法中:

    public final void onStartup(ServletContext servletContext) throws ServletException {
        String description = getDescription();
        if (!isEnabled()) {
            logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
            return;
        }
        register(description, servletContext);
    }
    

    进一步调用 DynamicRegistrationBean#register 方法:

    protected final void register(String description, ServletContext servletContext) {
        D registration = addRegistration(description, servletContext);
        if (registration == null) {
            logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
            return;
        }
        configure(registration);
    }
    

    然后会走到 ServletRegistrationBean#addRegistration() 方法中:

    protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
        String name = getServletName();
        return servletContext.addServlet(name, this.servlet);
    }
    

    这里,已经出现了 this.servlet 了,而其就是 DispatcherServlet 的实例,下面只需要看该对象是如何生成的就知道了:

    this.servlet.png

    注意到在 ServletRegistrationBean 的构造方法中,就有 T servlet,而这里的泛型 T 就是 DispatcherServlet,再看哪儿调用了这个构造方法(肯定只有子类 DispatcherServletRegistrationBean 的构造方法调 super 才可能)

    public ServletRegistrationBean(T servlet, boolean alwaysMapUrl, String... urlMappings) {
        Assert.notNull(servlet, "Servlet must not be null");
        Assert.notNull(urlMappings, "UrlMappings must not be null");
        this.servlet = servlet;
        this.alwaysMapUrl = alwaysMapUrl;
        this.urlMappings.addAll(Arrays.asList(urlMappings));
    }
    

    再看 DispatcherServletRegistrationBean 的构造方法:

    public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
        super(servlet);
        Assert.notNull(path, "Path must not be null");
        this.path = path;
        super.addUrlMappings(getServletUrlMapping());
    }
    

    再看哪儿调用了该构造方法,通过 Ctrl+B 可以看到,在 DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration 类中有调用:

    @Configuration(proxyBeanMethods = false)
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {
    
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                    webMvcProperties.getServlet().getPath());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }
    
    }
    

    很明显了,DispatcherServlet 对象就是在 DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration 类的 dispatcherServletRegistration(DispatcherServlet, WebMvcProperties, ObjectProvider<MultipartConfigElement>) 方法中被注入的。

    通过打断点可以知道,先到 DispatcherServletConfiguration#dispatcherServlet() 方法中创建DispatcherServlet 对象并放入 Spring 容器中;再到 DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration#dispatcherServletRegistration() 方法中,这里注入了上面生成的 DispatcherServlet 对象:

    创建 DispatcherServlet 对象.png
    注入 DispatcherServlet 对象.png

    结论

    • DispatcherServletConfiguration#dispatcherServlet() 方法中创建 DispatcherServlet 对象并放入 Spring 容器中;

    DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration#dispatcherServletRegistration() 方法中注入了 DispatcherServlet 对象。

    DispatcherServletRegistrationBean 对象注入到了哪儿

    接上面的问题,我们知道了创建 DispatcherServlet 的地方在 DispatcherServletConfiguration#dispatcherServlet(...) 方法中(以 @Bean 的方式),也知道了 DispatcherServlet 被注入到了 DispatcherServletRegistrationConfiguration#dispatcherServletRegistration(DispatcherServlet ...) 方法的入参里(该方法返回 DispatcherServletRegistrationBean 对象),那么 DispatcherServletRegistrationBean 又注入到了哪儿去了呢?

    再看一下 ServletWebServerApplicationContext#selfInitialize(ServletContext) 方法:

    private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareWebApplicationContext(servletContext);
        registerApplicationScope(servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
    

    getServletContextInitializerBeans() 方法,该方法返回一个 ServletContextInitializerBeans 对象(继承了 AbstractCollection<ServletContextInitializer>):

    protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
        return new ServletContextInitializerBeans(getBeanFactory());
    }
    

    再看 ServletContextInitializerBeans

    public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {
    
        private static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
    
        private static final Log logger = LogFactory.getLog(ServletContextInitializerBeans.class);
    
        /**
         * Seen bean instances or bean names.
         */
        private final Set<Object> seen = new HashSet<>();
    
        private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;
    
        private final List<Class<? extends ServletContextInitializer>> initializerTypes;
    
        private List<ServletContextInitializer> sortedList;
    
        @SafeVarargs
        @SuppressWarnings("varargs")
        public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
                Class<? extends ServletContextInitializer>... initializerTypes) {
            this.initializers = new LinkedMultiValueMap<>();
            this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
                    : Collections.singletonList(ServletContextInitializer.class);
            addServletContextInitializerBeans(beanFactory);
            addAdaptableBeans(beanFactory);
            List<ServletContextInitializer> sortedInitializers = this.initializers.values()
                .stream()
                .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
                .collect(Collectors.toList());
            this.sortedList = Collections.unmodifiableList(sortedInitializers);
            logMappings(this.initializers);
        }
    
        private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
            for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
                for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
                        initializerType)) {
                    addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
                }
            }
        }
    
        private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
                ListableBeanFactory beanFactory) {
            if (initializer instanceof ServletRegistrationBean) {
                Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
                addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
            }
            else if (initializer instanceof FilterRegistrationBean) {
                Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
                addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
            }
            else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
                String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
                addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
            }
            else if (initializer instanceof ServletListenerRegistrationBean) {
                EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();
                addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
            }
            else {
                addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
                        initializer);
            }
        }
    
        private void addServletContextInitializerBean(Class<?> type, String beanName, ServletContextInitializer initializer,
                ListableBeanFactory beanFactory, Object source) {
            this.initializers.add(type, initializer);
            if (source != null) {
                // Mark the underlying source as seen in case it wraps an existing bean
                this.seen.add(source);
            }
            if (logger.isTraceEnabled()) {
                String resourceDescription = getResourceDescription(beanName, beanFactory);
                int order = getOrder(initializer);
                logger.trace("Added existing " + type.getSimpleName() + " initializer bean '" + beanName + "'; order="
                        + order + ", resource=" + resourceDescription);
            }
        }
    
        private String getResourceDescription(String beanName, ListableBeanFactory beanFactory) {
            if (beanFactory instanceof BeanDefinitionRegistry) {
                BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
                return registry.getBeanDefinition(beanName).getResourceDescription();
            }
            return "unknown";
        }
    
        @SuppressWarnings("unchecked")
        protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
            MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
            addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
            addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
            for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
                addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
                        new ServletListenerRegistrationBeanAdapter());
            }
        }
    
        private MultipartConfigElement getMultipartConfig(ListableBeanFactory beanFactory) {
            List<Entry<String, MultipartConfigElement>> beans = getOrderedBeansOfType(beanFactory,
                    MultipartConfigElement.class);
            return beans.isEmpty() ? null : beans.get(0).getValue();
        }
    
        protected <T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
                RegistrationBeanAdapter<T> adapter) {
            addAsRegistrationBean(beanFactory, type, type, adapter);
        }
    
        private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
                Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
            List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
            for (Entry<String, B> entry : entries) {
                String beanName = entry.getKey();
                B bean = entry.getValue();
                if (this.seen.add(bean)) {
                    // One that we haven't already seen
                    RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
                    int order = getOrder(bean);
                    registration.setOrder(order);
                    this.initializers.add(type, registration);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
                                + order + ", resource=" + getResourceDescription(beanName, beanFactory));
                    }
                }
            }
        }
    
        private int getOrder(Object value) {
            return new AnnotationAwareOrderComparator() {
                @Override
                public int getOrder(Object obj) {
                    return super.getOrder(obj);
                }
            }.getOrder(value);
        }
    
        private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type) {
            return getOrderedBeansOfType(beanFactory, type, Collections.emptySet());
        }
    
        private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type,
                Set<?> excludes) {
            String[] names = beanFactory.getBeanNamesForType(type, true, false);
            Map<String, T> map = new LinkedHashMap<>();
            for (String name : names) {
                if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
                    T bean = beanFactory.getBean(name, type);
                    if (!excludes.contains(bean)) {
                        map.put(name, bean);
                    }
                }
            }
            List<Entry<String, T>> beans = new ArrayList<>(map.entrySet());
            beans.sort((o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(), o2.getValue()));
            return beans;
        }
    
        private void logMappings(MultiValueMap<Class<?>, ServletContextInitializer> initializers) {
            if (logger.isDebugEnabled()) {
                logMappings("filters", initializers, Filter.class, FilterRegistrationBean.class);
                logMappings("servlets", initializers, Servlet.class, ServletRegistrationBean.class);
            }
        }
    
        private void logMappings(String name, MultiValueMap<Class<?>, ServletContextInitializer> initializers,
                Class<?> type, Class<? extends RegistrationBean> registrationType) {
            List<ServletContextInitializer> registrations = new ArrayList<>();
            registrations.addAll(initializers.getOrDefault(registrationType, Collections.emptyList()));
            registrations.addAll(initializers.getOrDefault(type, Collections.emptyList()));
            String info = registrations.stream().map(Object::toString).collect(Collectors.joining(", "));
            logger.debug("Mapping " + name + ": " + info);
        }
    
        @Override
        public Iterator<ServletContextInitializer> iterator() {
            return this.sortedList.iterator();
        }
    
        @Override
        public int size() {
            return this.sortedList.size();
        }
    
        /**
         * Adapter to convert a given Bean type into a {@link RegistrationBean} (and hence a
         * {@link ServletContextInitializer}).
         *
         * @param <T> the type of the Bean to adapt
         */
        @FunctionalInterface
        protected interface RegistrationBeanAdapter<T> {
    
            RegistrationBean createRegistrationBean(String name, T source, int totalNumberOfSourceBeans);
    
        }
    
        /**
         * {@link RegistrationBeanAdapter} for {@link Servlet} beans.
         */
        private static class ServletRegistrationBeanAdapter implements RegistrationBeanAdapter<Servlet> {
    
            private final MultipartConfigElement multipartConfig;
    
            ServletRegistrationBeanAdapter(MultipartConfigElement multipartConfig) {
                this.multipartConfig = multipartConfig;
            }
    
            @Override
            public RegistrationBean createRegistrationBean(String name, Servlet source, int totalNumberOfSourceBeans) {
                String url = (totalNumberOfSourceBeans != 1) ? "/" + name + "/" : "/";
                if (name.equals(DISPATCHER_SERVLET_NAME)) {
                    url = "/"; // always map the main dispatcherServlet to "/"
                }
                ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(source, url);
                bean.setName(name);
                bean.setMultipartConfig(this.multipartConfig);
                return bean;
            }
    
        }
    
        /**
         * {@link RegistrationBeanAdapter} for {@link Filter} beans.
         */
        private static class FilterRegistrationBeanAdapter implements RegistrationBeanAdapter<Filter> {
    
            @Override
            public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) {
                FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(source);
                bean.setName(name);
                return bean;
            }
    
        }
    
        /**
         * {@link RegistrationBeanAdapter} for certain {@link EventListener} beans.
         */
        private static class ServletListenerRegistrationBeanAdapter implements RegistrationBeanAdapter<EventListener> {
    
            @Override
            public RegistrationBean createRegistrationBean(String name, EventListener source,
                    int totalNumberOfSourceBeans) {
                return new ServletListenerRegistrationBean<>(source);
            }
    
        }
    
    }
    

    在其构造方法里就通过 beanFactoryServletContextInitializer 相关的 bean 给取了出来,存入到了 initializers 成员里,而显然 DispatcherServletRegistrationBean 也实现了 ServletContextInitializer 接口,故在这里会把 DispatcherServletRegistrationBean 取出来。

    结论

    • 应用初始化时,会将 @Bean 提供的类创建出对象存入 beanFactory,这里就包含 DispatcherServletRegistrationBean 对象和 DispatcherServlet 对象
    • 创建 DispatcherServletRegistrationBean 对象时需要 DispatcherServlet 对象,DispatcherServlet 对象被注入到了对应的方法入参中
    • DispatcherServletRegistrationBean 并没有通过方法入参、Autowired 到成员变量等方式注入到某个对象中去,而是直接通过 beanFactory 取了出来,然后进行调用(ServletContextInitializer 接口的 onStartup 方法)。
    • onStartup 再往下会到 org.apache.catalina.core.ApplicationContext#addServlet() 方法,把 DispatcherServlet 对象传递给 Tomcat
    private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareWebApplicationContext(servletContext);
        registerApplicationScope(servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
    

    Springboot 和 Tomcat 的交互

    • Springboot 创建 ServletWebServerApplicationContext 对象,并调用 refresh 方法
    • ServletWebServerApplicationContext 调用 createWebServer() 方法创建 org.springframework.boot.web.server.WebServer 对象
    • createWebServer() 方法中会调用 factory.getWebServer(getSelfInitializer()),通过 getSelfInitializer() 注册了一个回调到 Tomcat 中去,其实是 ServletWebServerApplicationContextvoid selfInitialize(ServletContext servletContext) 方法,用以接收 Tomcat 传递过来的 ServletContext 对象(是一个接口),而 Tomcat 的 org.apache.catalina.core.ApplicationContext 实现了该接口
    • 这里的 factoryTomcatServletWebServerFactory,在 TomcatServletWebServerFactory#getWebServer() 方法中通过一系列调用,最终在 TomcatServletWebServerFactory#configureContext() 方法中将 Springboot 侧的 initializers(即 selfInitialize 方法回调)传递给了 Tomcat
    • 桥梁搭建完毕,Tomcat 持有 Springboot 的 selfInitialize 方法回调,Springboot 持有 Tomcat 的 ServletContext 对象(实现类为 ApplicationContext),二者通过该相互持有的对象进行交互调用。如 Springboot 调用 ServletContext#addServlet() 方法把创建出来的 DispatcherServlet 对象传递给 Tomcat
    • 简单来说,Springboot 创建 Tomcat 服务器并传递 selfInitialize 方法回调,Tomcat 通过该回调回传 ServletContext 对象,Springboot 再通过 ServletContext 对象传递 DispatcherServlet 对象给 Tomcat。

    相关文章

      网友评论

          本文标题:SpringMVC/SprintBoot 是如何将 Dispat

          本文链接:https://www.haomeiwen.com/subject/lckqidtx.html