概述

​ SpringBoot中集成官方的第三方组件是通过在POM文件中添加组件的starter的Maven依赖来完成的。添加相关的Maven依赖之后,会引入具体的jar包,在SpringBoot启动的时候会根据默认自动装配的配置类的注入条件判断是否注入该自动配置类到Spring容器中。自动配置类中会创建具体的第三方组件需要的类 。Tomcat和SpringMVC都是通过这样的方式进行集成的。SpringBoot出现之前SpringMVC项目是直接部署在Tomcat服务器中的,Tomcat是一个符合Servlet标准的Web服务器,Tomcat单独作为一个可安装软件。这种方式下Tomcat是一个完整独立的web服务器,SpringMVC项目不能脱离Web服务器直接运行,需要先部署在Tomcat服务器的目录中才能运行。SpringBoot出现之后改变了这种方式,我们可以直接运行SpringBoot项目,因为SpringBoot中可以内嵌tomcat服务器,而tomcat也是java开发的。在启动SpringBoot项目的时候直接创建tomcat服务并且把SpringMVC的项目部署在tomcat服务中。

一、自动装配原理

​ 在SpringBoot自动装配的程中,会加载/META-INF/factories文件中的以EnableAutoConfiguration为Key的配置类的字符串数组,在该类中会根据具体引入的服务器类进行创建具体的服务器对象。这些字符串数组是否真正加载进Spring容器,需要经过两方面的判断,

​ 1.根据META-INF/spring-autoconfigure-metadata.properties文件中配置的规则判断是否需要过滤。

​ 2.在org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass中会根据自动装配类的条件注解来判断是否进行自动加载。如果条件注解全部校验成功才会加载该配置类。

两种情况的底层校验逻辑都是一样的,都是通过调用OnWebApplicationCondition、OnClassCondition、OnBeanCondition的match方法进行判断。但是在processConfigurationClass方式的检查类型会更多,比如ConditionalOnMissingBean 等条件的检查。

条件注解 注解备注
OnWebApplicationCondition 判断是否符合服务器类型
OnClassCondition 判断项目中是否存在配置的类
OnBeanCondition 判断Spring容器中是否注入配置的类

二、内嵌式Tomcat注入

2.1自动注入配置类分析

1.ServletWebServerFactoryAutoConfiguration 配置类分析

​ 1.META-INF/factories文件中以EnableAutoConfiguration为Key的配置类的字符串数组,其中 ServletWebServerFactoryAutoConfiguration 为创建Web服务器的自动配置类,根据META-INF/spring-autoconfigure-metadata.properties文件中配置的规则判断是否需要过滤,该文件关于ServletWebServerFactoryAutoConfiguration的配置描述如下,

  1. org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration.ConditionalOnClass=javax.servlet.ServletRequest
  2. org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration.ConditionalOnWebApplication=SERVLET
  3. org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration.AutoConfigureOrder=-2147483648

此配置说明需要在当前的项目中有javax.servlet.ServletRequest类并且WebApplication是SERVLET容器,才不会过滤。

  1. 根据自动装配类的条件注解来判断是否进行自动加载,ServletWebServerFactoryAutoConfiguration 配置类的条件注解如下,
  1. @Configuration(proxyBeanMethods = false)
  2. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  3. // 判断是否有 ServletRequest 类
  4. @ConditionalOnClass(ServletRequest.class)
  5. // 判断 WebApplication 是 SERVLET 容器
  6. @ConditionalOnWebApplication(type = Type.SERVLET)
  7. @EnableConfigurationProperties(ServerProperties.class)

此配置说明需要在当前的项目中有javax.servlet.ServletRequest类并且WebApplication是SERVLET容器,才会进行ServletWebServerFactoryAutoConfiguration 类的加载。

2.2注入逻辑具体分析

  1. tomcat的starter依赖配置代码如下,
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-tomcat</artifactId>
  4. <version>2.2.5.RELEASE</version>
  5. <scope>compile</scope>
  6. </dependency>
  1. 添加Maven依赖配置后,会引入tomcat-embed-corejar包,其中有ServletRequest接口。代码截图如下,

SpringBoot的在创建服务器类型的时候是Servlet的,在创建SpringApplication的run方法中会调用createApplicationContext方法创建具体的AnnotationConfigServletWebServerApplicationContext类,该类继承了org.springframework.web.context.support.GenericWebApplicationContext类。OnWebApplicationCondition条件注解中就是通过判断是否有GenericWebApplicationContext类来检查是否是SERVLET类型的。

  1. 所以添加完tomcat的starter依赖配置后,ServletWebServerFactoryAutoConfiguration所有的条件注解都会匹配成功 ,即会自动加载 ServletWebServerFactoryAutoConfiguration 自动装配类。该类会通过@Import注解导入3个内嵌服务器的实现类,这3个实现类的服务器都是Servlet标准的。代码如下,
  1. // 导入具体服务器类
  2. @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
  3. ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
  4. ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
  5. ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })

三个Embeddedxxx表示相应的服务器实现类,每个服务器类都有自己的条件注解实现不同的加载逻辑,如果这里注入了多个web服务器,在服务器启动的时候会报错。EmbeddedTomcat类的代码如下,

  1. @Configuration(proxyBeanMethods = false)
  2. #当前工程下需要有Servlet、Tomcat UpgradeProtocol类
  3. @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
  4. #当前的Spring容器类不能有ServletWebServerFactory类型的Bean,搜索的是当前容器。
  5. @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
  6. static class EmbeddedTomcat {
  7. @Bean
  8. TomcatServletWebServerFactory tomcatServletWebServerFactory(
  9. ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
  10. ObjectProvider<TomcatContextCustomizer> contextCustomizers,
  11. ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
  12. // 创建 TomcatServletWebServerFactory ,是产生TomcatServletWebServer的工厂类
  13. TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
  14. factory.getTomcatConnectorCustomizers()
  15. .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
  16. factory.getTomcatContextCustomizers()
  17. .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
  18. factory.getTomcatProtocolHandlerCustomizers()
  19. .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
  20. return factory;
  21. }
  22. }

从以上代码看出,EmbeddedTomcat配置类能加载注入的条件是当前工程下需要有Servlet、Tomcat、 UpgradeProtocol3个类并且当前Spring容器没有注入ServletWebServerFactory类型的Bean。而这个三个类都在tomcat-embed-core.jar包中,并且Spring容器中之前没有注入ServletWebServerFactory类型的Bean,所会自动加载EmbeddedTomcat类。EmbeddedTomcat用来注入TomcatServletWebServerFactory到Spring容器,TomcatServletWebServerFactory的实现了ServletWebServerFactory接口,TomcatServletWebServerFactory可以创建具体的TomcatWebServer类。在SpringBoot启动的时候会从Spring容器中获取ServletWebServerFactory类型的Bean。至此Tomcat的自动装置解析完了。

三、SpringMVC注入

​ SpringMVC中最重要的组件是DispatchServlet,SpringMVC要在Servlet类型的Web服务器运行,就要把Servlet添加到Web容器中,用Servlet来处理请求。回想一下以前的SpringMVC,我们需要在web.xml配置文件中添加Servlet的配置,会默认把SpringMVC的DispatchServlet配置到Servlet配置节点中,web.xm配置节点代码如下,

  1. <servlet>
  2. <servlet-name>dispatcherServlet</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <init-param>
  5. <param-name>contextConfigLocation</param-name>
  6. <param-value>classpath:springmvc.xml</param-value>
  7. </init-param>
  8. <!--Web服务器一旦启动,Servlet就会实例化创建对象,然后初始化(预备创建对象)-->
  9. <load-on-startup>1</load-on-startup>
  10. </servlet>

在SpringBoot中我们使用的是内嵌式的Tomcat,那tomcat和SpringMVC的DispatchServlet是怎么关联起来的?一起往下看,META-INF/factories文件中的以EnableAutoConfiguration为Key的配置类的字符串数组,其中 DispatcherServletAutoConfiguration 和 WebMvcAutoConfiguration 是SpringMVC中最为重要的两个自动配置类,DispatcherServletAutoConfiguration 用来注册 DispatcherServlet到 Spring容器,WebMvcAutoConfiguration 用来注入MVC的相关组件到Spring容器。

3.1自动注入配置类分析

1.DispatcherServletAutoConfiguration 配置类分析

​ 1.1 根据META-INF/spring-autoconfigure-metadata.properties文件中配置的规则判断是否需要过滤,该文件关于DispatcherServletAutoConfiguration 的配置描述如下,

  1. #WebApplication是SERVLET容器
  2. org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.ConditionalOnWebApplication=SERVLET
  3. #工程中需要引入 DispatcherServlet 类
  4. org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet
  5. #容器注入在 ServletWebServerFactoryAutoConfiguration(服务器自动配置类) 类之后,
  6. org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
  7. org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.AutoConfigureOrder=-2147483648

DispatcherServletAutoConfiguration 的逻辑为:需要在当前的项目中有DispatcherServlet类并且WebApplication是SERVLET容器,才不会过滤掉。

​ 1.2 根据自动装配类的条件注解来判断是否进行自动加载,DispatcherServletAutoConfiguration 配置类的条件注解如下,

  1. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  2. @Configuration(proxyBeanMethods = false)
  3. @ConditionalOnWebApplication(type = Type.SERVLET)
  4. @ConditionalOnClass(DispatcherServlet.class)
  5. @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)

此逻辑需要在当前的项目中有DispatcherServlet类并且WebApplication是SERVLET容器,并且注入在 DispatcherServletAutoConfiguration 类之后,才会进行DispatcherServletAutoConfiguration 类的加载。在DispatcherServletAutoConfiguration 中有两个非常重要的内部类,DispatcherServletConfiguration 和DispatcherServletRegistrationConfiguration ,这两个内部类也会根据条件注解加载来决定是否加载进Spring容器中 。DispatcherServletConfiguration 主要用来注入 DispatcherServlet ;DispatcherServletRegistrationConfiguration 主要用来注入 DispatcherServletRegistrationBean,DispatcherServletRegistrationBean 用来包装 DispatcherServlet 类,它实现了ServletContextInitializer接口,用来注册Servlet对象到tomcat中的ServletContext对象。

2.DispatcherServletConfiguration 配置类分析

​ DispatcherServletConfiguration 中有个DispatcherServlet类型的@Bean对象,会把DispatcherServlet注入到Spring容器中,DispatcherServletConfiguration 代码如下,

  1. @Configuration(proxyBeanMethods = false)
  2. @Conditional(DefaultDispatcherServletCondition.class)
  3. @ConditionalOnClass(ServletRegistration.class)
  4. @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
  5. protected static class DispatcherServletConfiguration {
  6. @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
  7. public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
  8. DispatcherServlet dispatcherServlet = new DispatcherServlet();
  9. dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
  10. dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
  11. dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
  12. dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
  13. dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
  14. return dispatcherServlet;
  15. }
  16. }

以上条件注解代码可知,将BeanName为 dispatcherServlet ,类型为DispatcherServlet 的Bean注入到容器中的条件为:先查找是否已经容器中是否存在名称 dispatcherServlet的Bean或者类型DispatcherServlet 的Bean,如果都不存并且当前工程中有ServletRegistration类则将DispatcherServletConfiguration注入到Spring容器中,并且还会注入DispatcherServlet类。

  1. DispatcherServletRegistrationConfiguration 配置类分析

​ DispatcherServletRegistrationConfiguration 主要用来注入 DispatcherServletRegistrationBean,DispatcherServletRegistrationBean 用来包装 DispatcherServlet 类,它实现了ServletContextInitializer接口,SpringBoot用DispatcherServletRegistrationBean 来注册Servlet对象到Tomcat服务器中的ServletContext对象,在后面Tomcat启动的时候会讲具体实现。DispatcherServletRegistrationConfiguration代码如下,

  1. @Configuration(proxyBeanMethods = false)
  2. @Conditional(DispatcherServletRegistrationCondition.class)
  3. @ConditionalOnClass(ServletRegistration.class)
  4. @EnableConfigurationProperties(WebMvcProperties.class)
  5. @Import(DispatcherServletConfiguration.class)
  6. protected static class DispatcherServletRegistrationConfiguration {
  7. @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
  8. @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
  9. public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
  10. WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
  11. DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
  12. webMvcProperties.getServlet().getPath());
  13. registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
  14. registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
  15. multipartConfig.ifAvailable(registration::setMultipartConfig);
  16. return registration;
  17. }
  18. }

以上条件注解代码可知,将BeanName为 dispatcherServletRegistration,类型为DispatcherServletRegistrationBean的Bean注入到容器中的条件为:先查找是否已经容器中是否存在名称 dispatcherServletRegistration的Bean或者类型DispatcherServletRegistrationBean的Bean,如果都不存并且当前工程中有ServletRegistration类则将DispatcherServletRegistrationConfiguration注入到Spring容器中,并且还会注入DispatcherServletRegistrationBean类。

  1. WebMvcAutoConfiguration

​ 4. 1 根据META-INF/spring-autoconfigure-metadata.properties文件中配置的规则判断是否需要过滤,该文件关于 WebMvcAutoConfiguration 的配置描述如下,

  1. #工程中需要引入Servlet、WebMvcConfigurer、 DispatcherServlet类 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.ConditionalOnClass=javax.servlet.Servlet,org.springframework.web.servlet.config.annotation.WebMvcConfigurer,org.springframework.web.servlet.DispatcherServlet
  2. # 容器注入在 DispatcherServletAutoConfiguration、TaskExecutionAutoConfiguration、ValidationAutoConfiguration之
  3. # 后
  4. org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
  5. org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureOrder=-2147483638

WebMvcAutoConfiguration的逻辑为:需要在当前的项目中有Servlet、WebMvcConfigurer、DispatcherServlet3个类,才不会过滤掉。

​ 4.2 根据自动装配类的条件注解来判断是否进行自动加载,WebMvcAutoConfiguration配置类的条件注解如下,

  1. @Configuration(proxyBeanMethods = false)
  2. @ConditionalOnWebApplication(type = Type.SERVLET)
  3. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
  4. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
  5. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
  6. @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
  7. ValidationAutoConfiguration.class })

此逻辑需要当前的应用程序为SERVLET类型,当前的项目中有Servlet、DispatcherServlet、WebMvcConfigurer3个类、WebMvcConfigurationSupport没有被注入到Spring容器、在 DispatcherServletAutoConfiguration 、TaskExecutionAutoConfiguration、ValidationAutoConfiguration 类注入到Spring容器之后,才会进行WebMvcAutoConfiguration类的注入。在该中有个内部类WebMvcAutoConfigurationAdapter ,主要用来注入 SpringMVC的组件,比如ViewResolver、LocaleResolver等。

3.2 注入逻辑具体分析

  1. springmvc的starter依赖配置代码如下
  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-webmvc</artifactId>
  4. <version>5.2.4.RELEASE</version>
  5. <scope>compile</scope>
  6. </dependency>
  1. 添加Maven依赖配置后,会引入spring-webmvc.jar包,其中有DispatcherServlet类、WebMvcConfigurer接口。而springmvc的自动配置类在ServletWebServerFactoryAutoConfiguration配置类加载之后进行加载,所以肯定会有Servlet的存在。

  2. 所以添加完springmvc的Maven依赖配置后,会自动加载 DispatcherServletAutoConfiguration类和WebMvcAutoConfiguration类以及他们配置类中其他的配置类。至此SpringMVC的自动装置解析完了。

四、Tomcat与SpringMVC集成

  1. 在SpringApplication的run方法中,会进行ApplicationContext的创建,然后会执行 refreshContext 方法,其中的会执行onRefresh方法,这个方法是由子类ServletWebServerApplicationContext实现,ServletWebServerApplicationContext中onRefresh方法代码如下,
  1. protected void onRefresh() {
  2. super.onRefresh();
  3. try {
  4. // 创建 web 服务器
  5. createWebServer();
  6. }
  7. catch (Throwable ex) {
  8. throw new ApplicationContextException("Unable to start web server", ex);
  9. }
  10. }
  1. createWebServer里面的逻辑主要是创建web服务器,主要代码如下,
  1. private void createWebServer() {
  2. WebServer webServer = this.webServer;
  3. ServletContext servletContext = getServletContext();
  4. // 如果为 null,则创建
  5. if (webServer == null && servletContext == null) {
  6. // 获取WebServerFactory
  7. ServletWebServerFactory factory = getWebServerFactory();
  8. // 获取 webServer ,getSelfInitializer() 返回的是 函数式接口
  9. // 是一个 实现了@FunctionalInterface 接口的列表
  10. this.webServer = factory.getWebServer(getSelfInitializer());
  11. }
  12. else if (servletContext != null) {
  13. try {
  14. getSelfInitializer().onStartup(servletContext);
  15. }
  16. catch (ServletException ex) {
  17. throw new ApplicationContextException("Cannot initialize servlet context", ex);
  18. }
  19. }
  20. initPropertySources();
  21. }
  1. 创建服务器分为两步,第一步获取WebServerFactory 服务器工厂,第二步根据工厂获取具体的web服务器。getWebServerFactory()主要逻辑为:先获 BeanFactory ,然后从 BeanFactory取 ServletWebServerFactory 类型的 BeanNames数组,如果数组长度为 0,则抛异常,如果数组长度大于 1,则抛异常,最后根据第一个beanName和ServletWebServerFactory类型获取容器中的Bean进行返回。
  2. getWebServerFactory方法源码如下,
  1. protected ServletWebServerFactory getWebServerFactory() {
  2. // 先获 BeanFactory ,再从 BeanFactory取 ServletWebServerFactory 类型的 BeanNames
  3. String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
  4. // 如果长度为 0,则抛异常
  5. if (beanNames.length == 0) {
  6. throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
  7. + "ServletWebServerFactory bean.");
  8. }
  9. // 如果长度大于 1,则抛异常
  10. if (beanNames.length > 1) {
  11. throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
  12. + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
  13. }
  14. // 返回根据第一个beanName和ServletWebServerFactory类型获取容器中的Bean
  15. return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
  16. }

因为之前是注入了Tomcat的TomcatServletWebServerFactory,这里返回的是前面注入到容器里面的 TomcatServletWebServerFactory,然后调用该类的getWebServer方法,传入getSelfInitializer()方法参数,这个时候不会进行getSelfInitializer()里面具体业务逻辑调用,而是生成实现了ServletContextInitializer接口的匿名类后传入方法中。ServletContextInitializer接口代码如下,

  1. @FunctionalInterface
  2. public interface ServletContextInitializer {
  3. /**
  4. * Configure the given {@link ServletContext} with any servlets, filters, listeners
  5. * context-params and attributes necessary for initialization.
  6. * @param servletContext the {@code ServletContext} to initialize
  7. * @throws ServletException if any call against the given {@code ServletContext}
  8. * throws a {@code ServletException}
  9. */
  10. void onStartup(ServletContext servletContext) throws ServletException;
  11. }
  1. getSelfInitializer()方法返回的是函数表达式,不会立马执行方法内容。当tomcat服务器启动的时候会调用该匿名类的onStartup方法。getSelfInitializer方法中的主要逻辑为:找出实现了ServletContextInitializer 接口的类并注入容器 ,然后循环调用实现了ServletContextInitializer 接口的匿名类的onStartup()方法。在3.1 章节中注入了实现了ServletContextInitializer 接口的DispatcherServletRegistrationBean类,这里会调用DispatcherServletRegistrationBean的onStartup()方法,而DispatcherServletRegistrationBean中包含了3.1中注入的DispatcherServlet 类。DispatcherServletRegistrationBean的onStartup方法的主要逻辑为:将当前类中的DispatcherServlet 添加到Tomcat 的 servletContext 中。getSelfInitializer关键代码如下,
  1. private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
  2. return this::selfInitialize;
  3. }
  4. private void selfInitialize(ServletContext servletContext) throws ServletException {
  5. prepareWebApplicationContext(servletContext);
  6. registerApplicationScope(servletContext);
  7. // 注入 servletContext 类型的 bean
  8. WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
  9. // 注入实现了ServletContextInitializer 接口的类 ,并进行循环调用onStartup
  10. for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
  11. beans.onStartup(servletContext);
  12. }
  13. }

上面的getServletContextInitializerBeans()方法主要逻辑为:创建一个ServletContextInitializerBeans类,它实现了AbstractCollection接口,可以作为集合直接遍历。ServletContextInitializerBeans构造方法主要逻辑为:在容器中获取所有实现了ServletContextInitializer 接口的类添加到this.initializers中,排序后赋值给sortedList属性。getServletContextInitializerBeans()和ServletContextInitializerBeans的构造方法代码如下,

  1. //getServletContextInitializerBeans具体代码
  2. protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
  3. // 构造ServletContextInitializerBeans对象,该对象实现了 AbstractCollection<ServletContextInitializer> 接口
  4. // 是 ServletContextInitializer的 一个集合类。
  5. return new ServletContextInitializerBeans(getBeanFactory());
  6. }
  7. // ServletContextInitializerBeans 构造方法
  8. public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
  9. Class<? extends ServletContextInitializer>... initializerTypes) {
  10. // 初始化 initializers
  11. this.initializers = new LinkedMultiValueMap<>();
  12. this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
  13. : Collections.singletonList(ServletContextInitializer.class);
  14. // 在beanFactory中查找实现了 ServletContextInitializer 接口的类
  15. // 这里包括了 ServletRegistrationBean 、FilterRegistrationBean
  16. // DelegatingFilterProxyRegistrationBean、ServletListenerRegistrationBean 和其他。
  17. // 添加到 initializers 集合中
  18. addServletContextInitializerBeans(beanFactory);
  19. // 添加适配的 Bean
  20. addAdaptableBeans(beanFactory);
  21. // initializers 排序
  22. List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
  23. .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
  24. .collect(Collectors.toList());
  25. // 给该集合可遍历对象赋值
  26. this.sortedList = Collections.unmodifiableList(sortedInitializers);
  27. // 返回 initializers
  28. logMappings(this.initializers);
  29. }

addServletContextInitializerBeans()方法中会找到所有实现了 ServletContextInitializer 接口的类,即会找到3.1章节中解析的DispatcherServletRegistrationBean类,都添加到 this.initializers 集合中。

​ 5.1DispatcherServletRegistrationBean的onStartup方法主要逻辑为:把当前的DispacherServlet对象添加到从参数传进来的ServletContext对象中,onStartup具体代码如下,

  1. public final void onStartup(ServletContext servletContext) throws ServletException {
  2. String description = getDescription();
  3. if (!isEnabled()) {
  4. logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
  5. return;
  6. }
  7. // 注册 Servlet 到 tomcat的 servletContext中
  8. register(description, servletContext);
  9. }

​ 5.2 register()方法是具体注册当前Servlet到tomcat的 servletContext中,代码如下,

  1. @Override
  2. protected final void register(String description, ServletContext servletContext) {
  3. // 添加当前的 servlet 到 tomcat 的 servletContext 中
  4. D registration = addRegistration(description, servletContext);
  5. if (registration == null) {
  6. logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
  7. return;
  8. }
  9. configure(registration);
  10. }

​ 5.3 addRegistration方法是先获取servlet名称再注册当前Servlet到参数传进来的的servletContext参数中,代码如下

  1. @Override
  2. protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
  3. // 获取 当前对象的 servlet 名称
  4. String name = getServletName();
  5. //添加当前的 servlet 到 tomcat 的 servletContext 中
  6. return servletContext.addServlet(name, this.servlet);
  7. }

​ 5.4总结一下,这个getSelfInitializer()很关键,主要逻辑为:把springmvc中的DispatcherServlet 添加到tomcat服务器中的servletContext对象中 。

  1. getWebServer方法用来获取WebServer,大概逻辑是:先创建Tomcat类,再初始化相关属性,最后创建TomcatWebServer对象。具体的代码如下,
  1. public WebServer getWebServer(ServletContextInitializer... initializers) {
  2. if (this.disableMBeanRegistry) {
  3. Registry.disableRegistry();
  4. }
  5. // 创建具体的Tomcat类
  6. Tomcat tomcat = new Tomcat();
  7. // 获取主路径,并且设置主路径
  8. File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
  9. tomcat.setBaseDir(baseDir.getAbsolutePath());
  10. // 创建连接器和协议
  11. Connector connector = new Connector(this.protocol);
  12. connector.setThrowOnFailure(true);
  13. // 添加连接器
  14. tomcat.getService().addConnector(connector);
  15. customizeConnector(connector);
  16. tomcat.setConnector(connector);
  17. // 设置 host 的自动部署属性为 false
  18. tomcat.getHost().setAutoDeploy(false);
  19. // 配置 engine
  20. configureEngine(tomcat.getEngine());
  21. for (Connector additionalConnector : this.additionalTomcatConnectors) {
  22. tomcat.getService().addConnector(additionalConnector);
  23. }
  24. // 初始化 TomcatEmbeddedContext ,并把 TomcatEmbeddedContext 添加至 host 中
  25. // 用 initializersToUse 初始化 TomcatStarter ,并设置 TomcatEmbeddedContext 的Starter属性为 TomcatStarter
  26. // initializersToUse 为函数式接口 ,即 实现了ServletContextInitializer接口 的匿名类
  27. prepareContext(tomcat.getHost(), initializers);
  28. // 创建具体的 TomcatwebServer
  29. return getTomcatWebServer(tomcat);
  30. }
  1. prepareContext()方法是创建WebServer前的准备 ,主要逻辑为:创建 TomcatEmbeddedContext ,并把 TomcatEmbeddedContext 添加至 host 中,然后用 initializersToUse (initializersToUse 为实现了ServletContextInitializer接口 的集合,其中包括了getWebServer方法中的参数 initializers )初始化 TomcatStarter ,并设置 TomcatEmbeddedContext 的Starter属性为TomcatStarter。即TomcatEmbeddedContext的Starter属性为TomcatStarter。在tomcat启动的时候会调用到TomcatStarter的onStartup方法。
  2. getTomcatWebServer()中会创建TomcatwebServer类,并且传入 tomcat 和 端口是否大于等于0 两个参数,代码如下
  1. protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
  2. return new TomcatWebServer(tomcat, getPort() >= 0);
  3. }

TomcatWebServer的构造函数中,除了给tomcat和 autoStart赋值,还会调用初始化方法initialize,TomcatWebServer的构造方法代码如下,

  1. public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
  2. Assert.notNull(tomcat, "Tomcat Server must not be null");
  3. this.tomcat = tomcat;
  4. // 自动开启
  5. this.autoStart = autoStart;
  6. // 初始化方法
  7. initialize();
  8. }
  1. initialize()方法中会进行TomcatWebServer的初始,关键代码如下,
  1. // Start the server to trigger initialization listeners
  2. // tomcat 服务器启动
  3. this.tomcat.start();

整个tomcat的启动比较复杂,有兴趣的可以去研究下tomcat源码,这里不做多讲直接给出结论。其中会调用上面创建的TomcatStarter类的onStartup方法,并传入tomcat的servletContext对象。TomcatStarter的onStartup具体代码如下,

  1. public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
  2. try {
  3. // 遍历已经注册的 initializers,
  4. for (ServletContextInitializer initializer : this.initializers) {
  5. initializer.onStartup(servletContext);
  6. }
  7. }
  8. catch (Exception ex) {
  9. this.startUpException = ex;
  10. // Prevent Tomcat from logging and re-throwing when we know we can
  11. // deal with it in the main thread, but log for information here.
  12. if (logger.isErrorEnabled()) {
  13. logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
  14. + ex.getMessage());
  15. }
  16. }
  17. }

onStartup方法中会遍历 initializers 集合并且调用其onStartup()方法,而initializers包括了我们在调用getWebServer时传入的getSelfInitializer方法,该方法体的主要业务逻辑上面已经讲了(请看本章的第5点),就是给传进去的servletContext添加当前Spring容器中注入的SpringMVC的DispatchServlet类。

  1. 最后tomcat完成初始化,会监听一个具体的端口。至此,tomcat启动的过程已经把SpringMVC的DispatchServlet类添加到了tomcat的servletContext对象中。当请求进来Web服务器的时候会转到DispatchServlet中。DispatchServlet的整个初始化过程这里不细讲了,请参考 SpringMVC请求流程源码分析 文章。

SpringBoot中Tomcat和SpringMVC整合源码分析的更多相关文章

  1. Tomcat处理HTTP请求源码分析(下)

    转载:http://www.infoq.com/cn/articles/zh-tomcat-http-request-2 很多开源应用服务器都是集成tomcat作为web container的,而且对 ...

  2. Tomcat处理HTTP请求源码分析(上)

    Tomcat处理HTTP请求源码分析(上) 作者 张华 发布于 2011年12月8日 | 8 讨论 分享到: 微博 微信 Facebook Twitter 有道云笔记 邮件分享 稍后阅读 我的阅读清单 ...

  3. springboot bean的循环依赖实现 源码分析

    springboot bean的循环依赖实现 源码分析 本文基于springboot版本2.5.1 <parent> <groupId>org.springframework. ...

  4. RocketMQ中Broker的HA策略源码分析

    Broker的HA策略分为两部分①同步元数据②同步消息数据 同步元数据 在Slave启动时,会启动一个定时任务用来从master同步元数据 if (role == BrokerRole.SLAVE) ...

  5. eclipse中tomcat调试正确关联源码

    1.build path中jar包关联本地源码 2.tomcat中添加source关联工程lib下的jar包 以上两步即可. 可解决tomcat直接关联本地源码debug时无法计算表达式的情况. 错误 ...

  6. Tomcat处理HTTP请求源码分析(上)(转)

    转载自:http://www.infoq.com/cn/articles/zh-tomcat-http-request-1 很多开源应用服务器都是集成tomcat作为web container的,而且 ...

  7. 详解Tomcat系列(一)-从源码分析Tomcat的启动

    在整个Tomcat系列文章讲解之前, 我想说的是虽然整个Tomcat体系比较复杂, 但是Tomcat中的代码并不难读, 只要认真花点功夫, 一定能啃下来. 由于篇幅的原因, 很难把Tomcat所有的知 ...

  8. 【MyBatis】MyBatis Tomcat JNDI原理及源码分析

    一. Tomcat JNDI JNDI(java nameing and drectory interface),是一组在Java应用中访问命名和服务的API,所谓命名服务,即将对象和名称联系起来,使 ...

  9. Fabric2.2中的Raft共识模块源码分析

    引言 Hyperledger Fabric是当前比较流行的一种联盟链系统,它隶属于Linux基金会在2015年创建的超级账本项目且是这个项目最重要的一个子项目.目前,与Hyperledger的另外几个 ...

随机推荐

  1. 简述Web3.0

    什么是 Web 3.0 以及为什么要关心它. 为了更好地理解什么是 Web 3.0,我们需要知道什么是 Web 1.0 和 2.0. 为了不让你厌烦,这里简单的解释一下: Web 1.0 -- 信息仅 ...

  2. Redisson报错

    org.redisson.client.RedisResponseTimeoutException: Redis server response timeout (3000 ms) occured a ...

  3. docker+nginx+redis部署前后端分离项目!!!

    介绍本文用的经典的前后端分离开源项目.项目的拉取这些在另一篇博客!!! 其中所需要的前后端打包本篇就不做操作了!!不明白的去看另一篇博客!!! 地址:http://www.cnblogs.com/ps ...

  4. OpenHarmony3.1 Release版本特性解析——硬件资源池化架构介绍

    李刚 OpenHarmony 分布式硬件管理 SIG 成员 华为技术有限公司分布式硬件专家 OpenHarmony 作为面向全场景.全连接.全智能时代的分布式操作系统,通过将各类不同终端设备的能力进行 ...

  5. Fail2ban 配置详解 过滤器配置

    Fail2ban自带了很多相关服务日志的过滤器. ### # 包含配置 ### [INCLUDES] before = common.conf # 还包含其他文件中的配置,在加载本配置文件中配置之前先 ...

  6. 【单片机】CH32V103C8T6定时器3程序实验

    代码功能:每隔1毫秒进入一次定时器中断. 每隔1秒串口打印一次数据. time.c #include "time.h" #include "ch32v10x.h" ...

  7. Vue路由的安装

    1.在Vue ui中插件中找到添加vue-router 2.安装以后,项目中的会自动完成配置. 3.在store中的index.js配置路由页面以及路径.

  8. Kube-OVN v1.10.0:新增Windows节点支持,用户自定义子网ACL等10+硬核功能

    在Kube-OVN社区小伙伴的共同努力下,Kube-OVN v1.10.0于五月份正式发布.Kube-OVN v1.10.0版本中,我们一如既往地对Kube-OVN 的功能.性能.稳定性和易用性进行了 ...

  9. 清明欢乐赛(USACO选题)

    总传送门 T1. [USACO19JAN] Redistricting P luogu P5202 思路: 这种每次选出段长有个上限\(k\)的常常是和单调队列有关. 这里是单调队列优化dp 不过一开 ...

  10. poj1475 -- Pushing Boxes

    这道题其实挺有趣 的,这让我想起小时候诺基亚手机上的推箱子游戏(虽然一点也不好玩) (英文不好-->)  题意翻译: 初始人(S),箱子(B),目的地(T)用人把箱子推到 T最小步数及其路径(满 ...