SpringBoot嵌入式Tomcat的自动配置原理

26次阅读

共计 9538 个字符,预计需要花费 24 分钟才能阅读完成。

在读本篇文章之前如果你读过这篇文章 SpringBoot 自动装配原理解析应该会更加轻松

准备工作

我们知道 SpringBoot 的自动装配的秘密在 org.springframework.boot.autoconfigure 包下的 spring.factories 文件中,而嵌入 Tomcat 的原理就在这个文件中加载的一个配置类:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {return new ServletWebServerFactoryCustomizer(serverProperties);
    }

    @Bean
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }

    /**
     * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
     * {@link ImportBeanDefinitionRegistrar} for early registration.
     */
    public static class BeanPostProcessorsRegistrar
            implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

        private ConfigurableListableBeanFactory beanFactory;

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {if (beanFactory instanceof ConfigurableListableBeanFactory) {this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
            }
        }

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                BeanDefinitionRegistry registry) {if (this.beanFactory == null) {return;}
            registerSyntheticBeanIfMissing(registry,
                    "webServerFactoryCustomizerBeanPostProcessor",
                    WebServerFactoryCustomizerBeanPostProcessor.class);
            registerSyntheticBeanIfMissing(registry,
                    "errorPageRegistrarBeanPostProcessor",
                    ErrorPageRegistrarBeanPostProcessor.class);
        }

        private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
                String name, Class<?> beanClass) {
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }
        }

    }

}

首先看一下上方的几个注解

  1. @AutoConfigureOrder这个注解是决定配置类的加载顺序的,当注解里的值越小越先加载,而 Ordered.HIGHEST_PRECEDENCE 的值是 Integer.MIN_VALUE 也就是说这个类肯定是最先加载的那一批
  2. @ConditionalOnXXX在之前的文章中已经无数次提到了,就不再阐述了
  3. @EnableConfigurationProperties开启 ServerProperties 类的属性值配置。而这个类里面包含的就是 Web 服务的配置
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

    private Integer port;

    private InetAddress address;

    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();

    private Boolean useForwardHeaders;

    private String serverHeader;

    private int maxHttpHeaderSize = 0; // bytes

    private Duration connectionTimeout;

    @NestedConfigurationProperty
    private Ssl ssl;

    @NestedConfigurationProperty
    private final Compression compression = new Compression();

    @NestedConfigurationProperty
    private final Http2 http2 = new Http2();

    private final Servlet servlet = new Servlet();

    private final Tomcat tomcat = new Tomcat();

    private final Jetty jetty = new Jetty();

    private final Undertow undertow = new Undertow();}

这个类的代码太多了,这里就不一一贴出来了,我们平常在 application.properties 中配置的 server.xxx 就是这个类中属性

  1. @Import引入了 4 个类,看都是什么吧

    1. BeanPostProcessorsRegistrar
public static class BeanPostProcessorsRegistrar
            implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                BeanDefinitionRegistry registry) {if (this.beanFactory == null) {return;}
            registerSyntheticBeanIfMissing(registry,
                    "webServerFactoryCustomizerBeanPostProcessor",
                    WebServerFactoryCustomizerBeanPostProcessor.class);
            registerSyntheticBeanIfMissing(registry,
                    "errorPageRegistrarBeanPostProcessor",
                    ErrorPageRegistrarBeanPostProcessor.class);
        }

        private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
                String name, Class<?> beanClass) {
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }
        }

    }

这个类注册了两个 bean:WebServerFactoryCustomizerBeanPostProcessorErrorPageRegistrarBeanPostProcessor 关于这两个 bean 的作用稍后再详细介绍

  1. EmbeddedTomcat
@Configuration
    @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedTomcat {

        @Bean
        public TomcatServletWebServerFactory tomcatServletWebServerFactory() {return new TomcatServletWebServerFactory();
        }

    }

这个类会在存在 Tomcat 相关 jar 包时添加一个TomcatServletWebServerFactorybean

其他两个相信大家都知道怎么回事了

  1. 除了这些这个类还注入了两个类 ServletWebServerFactoryCustomizerTomcatServletWebServerFactoryCustomizer

现在前期准备工作已经做好了,看一下这个 Tomcat 是如何启动的吧

启动

启动入口在 ServletWebServerApplicationContext 中的 onRefresh 方法

protected void onRefresh() {super.onRefresh();
        try {createWebServer();
        }
        catch (Throwable ex) {throw new ApplicationContextException("Unable to start web server", ex);
        }
    }

Tomcat 的启动就在 createWebServer 方法里面了

private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
          // 第一次访问的时候两个对象都为空
        if (webServer == null && servletContext == null) {ServletWebServerFactory factory = getWebServerFactory();
            this.webServer = factory.getWebServer(getSelfInitializer());
        }
        else if (servletContext != null) {
            try {getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context",
                        ex);
            }
        }
        initPropertySources();}

首先看一下getWebServerFactory

protected ServletWebServerFactory getWebServerFactory() {
        // 这里获取的 beanname 就是上方注册的 tomcatServletWebServerFactory 了
        String[] beanNames = getBeanFactory()
                .getBeanNamesForType(ServletWebServerFactory.class);
        if (beanNames.length == 0) {
            throw new ApplicationContextException(
                    "Unable to start ServletWebServerApplicationContext due to missing"
                            + "ServletWebServerFactory bean.");
        }
        if (beanNames.length > 1) {
            throw new ApplicationContextException(
                    "Unable to start ServletWebServerApplicationContext due to multiple"
                            + "ServletWebServerFactory beans :"
                            + StringUtils.arrayToCommaDelimitedString(beanNames));
        }
        return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    }

准备环境里注册的 bean 现在出来一个了。注意,上方还注册了一个后置处理器 EmbeddedServletContainerCustomizerBeanPostProcessor,获取 beantomcatServletWebServerFactory 的时候就会执行后置处理器的 postProcessBeforeInitialization 方法

public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {if (bean instanceof WebServerFactory) {postProcessBeforeInitialization((WebServerFactory) bean);
    }
    return bean;
}
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        LambdaSafe
                .callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
                        webServerFactory)
                .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
                .invoke((customizer) -> customizer.customize(webServerFactory));
    }

    private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {if (this.customizers == null) {
            // Look up does not include the parent context
            this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
            this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }
        return this.customizers;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {return (Collection) this.beanFactory
                .getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();}

这个处理器的作用是获得所有定制器,然后执行定制器的方法

接着往下看

这个时候就可以启动 Tomcat 了

public WebServer getWebServer(ServletContextInitializer... initializers) {Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null ? this.baseDirectory
                : createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        return getTomcatWebServer(tomcat);
    }
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {return new TomcatWebServer(tomcat, getPort() >= 0);
    }
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        initialize();}
private void initialize() throws WebServerException {
        TomcatWebServer.logger
                .info("Tomcat initialized with port(s):" + getPortsDescription(false));
        synchronized (this.monitor) {
            try {addInstanceIdToEngineName();

                Context context = findContext();
                context.addLifecycleListener((event) -> {if (context.equals(event.getSource())
                            && Lifecycle.START_EVENT.equals(event.getType())) {
                        // Remove service connectors so that protocol binding doesn't
                        // happen when the service is started.
                        removeServiceConnectors();}
                });

                // Start the server to trigger initialization listeners
                this.tomcat.start();

                // We can re-throw failure exception directly in the main thread
                rethrowDeferredStartupExceptions();

                try {ContextBindings.bindClassLoader(context, context.getNamingToken(),
                            getClass().getClassLoader());
                }
                catch (NamingException ex) {// Naming is not enabled. Continue}

                // Unlike Jetty, all Tomcat threads are daemon threads. We create a
                // blocking non-daemon to stop immediate shutdown
                startDaemonAwaitThread();}
            catch (Exception ex) {throw new WebServerException("Unable to start embedded Tomcat", ex);
            }
        }
    }

正文完
 0