SpringMVC/SprintBoot 是如何将 Dispat

2023-10-17  本文已影响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();
}

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

结论

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 取出来。

结论

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

Springboot 和 Tomcat 的交互

上一篇下一篇

猜你喜欢

热点阅读