springboot学习之旅05-Spring Boot 2.0.2 版本嵌入式Servlet容器自动配置及启动原理分析

作者:mujun_
链接:https://www.jianshu.com/p/017a7f40efff
来源:简书

Spring Boot 2.0.2 版本的嵌入式Servlet容器自动配置是通过WebServerFactoryCustomizer定制器来定制的,而在Spring Boot 1.x 版本中我们是通过EmbeddedServletContainerCustomizer嵌入式的Servlet容器定制器来定制的。网上已经有很多对1.x版本自动配置及启动原理的分析,对2.x版本后的原理分析是很难找到的,目前Spring官网推荐使用Spring Boot 2.0.2 版本,下面将结合这个版本的源码,来对内嵌的Servlet容器的自动配置及启动作一个分析。

嵌入式Servlet容器自动配置原理

Spring Boot 通过运行主程序类的main()方法来启动Spring Boot 应用,应用启动后调用ServletWebServerApplicationContext这个类中的createWebServer()方法:

private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = this.getServletContext();
        if (webServer == null && servletContext == null) {
                ServletWebServerFactory factory = this.getWebServerFactory();
                this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } else if (servletContext != null) {
                try {
                        this.getSelfInitializer().onStartup(servletContext);
                } catch (ServletException var4) {
                        throw new ApplicationContextException("Cannot initialize servlet context", var4);
                }
        }
        this.initPropertySources();
} 

该方法最终能够获取到一个与当前应用所导入的Servlet类型相匹配的web服务工厂定制器,例如你的pom文件导入的Servlet依赖为Tomcat:
springboot学习之旅05-Spring Boot 2.0.2 版本嵌入式Servlet容器自动配置及启动原理分析_第1张图片
那么最终会生成Tomcat web服务工厂定制器,定制Servlet容器配置,并通过上述代码中的getWebServer()方法创建对应的Servlet容器,并启动容器。

ServletWebServerFactory factory = this.getWebServerFactory();

上述代码执行来到EmbeddedWebServerFactoryCustomizerAutoConfiguration(嵌入式web服务工厂定制器自动配置类),根据导入的依赖信息,该配置类会自动创建相应类型的容器工厂定制器(目前Spring Boot 2.x 版本支持tomcat、jetty、undertow、netty),以tomcat为例,这里会创建TomcatWebServerFactoryCustomizer组件:

//导入的Servlet依赖为Tomcat,则创建Tomcat web服务工厂定制器
@ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
public static class TomcatWebServerFactoryCustomizerConfiguration {
    public TomcatWebServerFactoryCustomizerConfiguration() {
    }

    @Bean
    public TomcatWebServerFactoryCustomizertomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
        return new TomcatWebServerFactoryCustomizer(environment,serverProperties);
    }
}

需要注意的是,该自动配置类有一个@EnableConfigurationProperties注解:

@EnableConfigurationProperties({ServerProperties.class})
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {

该注解用于启动指定类ServerProperties(Servlet容器相关的配置类)中的ConfigurationProperties功能,将配置文件中对应的属性值与配置类中的属性值进行映射,并将该配置类添加到IOC容器中:

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties {

至此,TomcatWebServerFactoryCustomizer组件创建完成,对应的服务配置类也已添加到IOC容器。
下面的处理涉及到一个非常重要的类–>WebServerFactoryCustomizerBeanPostProcessor(web服务工厂定制器组件的后置处理器),该类负责在bean组件初始化之前执行初始化工作。
该类先从IOC容器中获取所有类型为WebServerFactoryCustomizer(web服务工厂定制器)的组件:

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

通过源码的调试,从IOC容器中获取到5个定制器,其中包含有前面创建的TomcatWebServerFactoryCustomizer定制器:

springboot学习之旅05-Spring Boot 2.0.2 版本嵌入式Servlet容器自动配置及启动原理分析_第2张图片获取到所有的定制器后,后置处理器调用定制器的customize()方法来给嵌入式的Servlet容器进行配置(默认或者自定义的配置):

private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
    ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
        customizer.customize(webServerFactory);
    });
}

TomcatWebServerFactoryCustomizer定制器调用customize()定制方法,获取到Servlet容器相关配置类ServerProperties,进行自动配置:

public void customize(ConfigurableTomcatWebServerFactory factory) {
    ServerProperties properties = this.serverProperties;
    Tomcat tomcatProperties = properties.getTomcat();
    PropertyMapper propertyMapper = PropertyMapper.get();
    tomcatProperties.getClass();
    propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory);
    tomcatProperties.getClass();
    propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds).as(Long::intValue).to(factory::setBackgroundProcessorDelay);
    this.customizeRemoteIpValve(factory);
    tomcatProperties.getClass();
    propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive).to((maxThreads) -> {
        this.customizeMaxThreads(factory, tomcatProperties.getMaxThreads());
    });
    tomcatProperties.getClass();
    propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive).to((minSpareThreads) -> {
        this.customizeMinThreads(factory, minSpareThreads);
    });
    propertyMapper.from(() -> {
        return this.determineMaxHttpHeaderSize();
    }).when(this::isPositive).to((maxHttpHeaderSize) -> {
        this.customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize);
    });
    tomcatProperties.getClass();
    propertyMapper.from(tomcatProperties::getMaxHttpPostSize).when((maxHttpPostSize) -> {
        return maxHttpPostSize != 0;
    }).to((maxHttpPostSize) -> {
        this.customizeMaxHttpPostSize(factory, maxHttpPostSize);
    });
    tomcatProperties.getClass();
    propertyMapper.from(tomcatProperties::getAccesslog).when(Accesslog::isEnabled).to((enabled) -> {
        this.customizeAccessLog(factory);
    });
    tomcatProperties.getClass();
    propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding);
    properties.getClass();
    propertyMapper.from(properties::getConnectionTimeout).whenNonNull().to((connectionTimeout) -> {
        this.customizeConnectionTimeout(factory, connectionTimeout);
    });
    tomcatProperties.getClass();
    propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive).to((maxConnections) -> {
        this.customizeMaxConnections(factory, maxConnections);
    });
    tomcatProperties.getClass();
    propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive).to((acceptCount) -> {
        this.customizeAcceptCount(factory, acceptCount);
    });
    this.customizeStaticResources(factory);
    this.customizeErrorReportValve(properties.getError(), factory);
}

至此,嵌入式Servlet容器的自动配置完成。
从源码分析可以得出配置嵌入式Servlet容器的两种解决方案:
1、在全局配置文件中,通过server.xxx来修改和server有关的配置:

server.port=8081
server.tomcat.xxx...

2、实现WebServerFactoryCustomizer接口,重写它的customize()方法,对容器进行定制配置:

@FunctionalInterface
public interface WebServerFactoryCustomizer<T extends WebServerFactory> {
    void customize(T factory);
}

嵌入式Servlet容器启动原理

分析ServletWebServerApplicationContext类的createWebServer()方法:

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = this.getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = this.getWebServerFactory();
        //执行下面的代码,创建嵌入式的Servlet容器
        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
    } else if (servletContext != null) {
        try {
            this.getSelfInitializer().onStartup(servletContext);
        } catch (ServletException var4) {
            throw new ApplicationContextException("Cannot initialize servlet context", var4);
        }
    }
    this.initPropertySources();
}

应用启动后,根据导入的依赖信息,创建了相应的Servlet容器工厂,以tomcat为例,创建嵌入式的Tomcat容器工厂TomcatServletWebServerFactory,调用getWebServer()方法创建Tomcat容器:

public WebServer getWebServer(ServletContextInitializer... initializers) {
    //创建嵌入式的Tomcat容器
    Tomcat tomcat = new Tomcat();
    File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    this.customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    this.configureEngine(tomcat.getEngine());
    Iterator var5 = this.additionalTomcatConnectors.iterator();
    while(var5.hasNext()) {
        Connector additionalConnector = (Connector)var5.next();
        tomcat.getService().addConnector(additionalConnector);
    }
    this.prepareContext(tomcat.getHost(), initializers);
    return this.getTomcatWebServer(tomcat);
}
...
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    //调用TomcatWebServer类的有参构造器
    //如果配置的端口号>=0,创建Tomcat容器
    return new TomcatWebServer(tomcat, this.getPort() >= 0);
}
...

分析TomcatWebServer类的有参构造器:

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    this.monitor = new Object();
    this.serviceConnectors = new HashMap();
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    this.initialize();
    }

执行initialize(),调用start()方法完成了tomcat容器的启动:

private void initialize() throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
    Object var1 = this.monitor;
    synchronized(this.monitor) {
        try {
            this.addInstanceIdToEngineName();
            Context context = this.findContext();
            context.addLifecycleListener((event) -> {
                if (context.equals(event.getSource()) && "start".equals(event.getType())) {
                    this.removeServiceConnectors();
                }
            });
            //启动tomcat容器
            this.tomcat.start();
            this.rethrowDeferredStartupExceptions();

总结:
1、Spring Boot 根据导入的依赖信息,自动创建对应的web服务工厂定制器;
2、web服务工厂定制器组件的后置处理器获取所有类型为web服务工厂定制器的组件(包含实现WebServerFactoryCustomizer接口,自定义的定制器组件),依次调用customize()定制接口,定制Servlet容器配置;
3、嵌入式的Servlet容器工厂创建tomcat容器,初始化并启动容器。

你可能感兴趣的:(springboot学习之旅05-Spring Boot 2.0.2 版本嵌入式Servlet容器自动配置及启动原理分析)