我们知道,springboot默认使用的是内嵌的tomcat作为web服务器,我们可以通过配置文件对tomcat的参数进行修改,比如server.port表示监听端口,server.contextPath表示上下文路径,server.tomcat.max-connections表示,完整配置可参考 https://docs.spring.io/spring-boot/docs/1.5.14.RELEASE/reference/htmlsingle/
spring-boot加载tomcat的过程如下:
(1) springboot的主函数有一个注解 @SpringBootApplication,而这个注解里有一个@EnableAutoConfiguration,所有的故事就从这里开始吧。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration //开启自动配置
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
(2) EnableAutoConfiguration会触发许多自动配置,其中就包括了EmbeddedServletContainerAutoConfiguration(至于EnableAutoConfiguration是怎么触发各个自动配置类的可以参考 spring-boot-autoconfigure)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class) //引入BeanPostProcessorsRegistrar,见下面的代码
public class EmbeddedServletContainerAutoConfiguration {
@Configuration
// 默认情况下,Servlet.class和Tomcat.class都在classpath中,因此这个条件满足
@ConditionalOnClass({ Servlet.class, Tomcat.class })
// 若用户没有自定义的EmbeddedServletContainerFactory,则继续往下
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
// 如上面的@ConditionalOnClass和@ConditionalOnMissingBean,则实例化一个TomcatEmbeddedServletContainerFactory
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
// ... 省略无关代码 ...
// 通过上面的@Import会引入这个BeanPostProcessorsRegistrar
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;
}
// 关键是这里,确保EmbeddedServletContainerCustomizerBeanPostProcessor实例存在,若不存在则实例化一个,因此这个类很重要,之后需要关注一下
registerSyntheticBeanIfMissing(registry,
"embeddedServletContainerCustomizerBeanPostProcessor",
EmbeddedServletContainerCustomizerBeanPostProcessor.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);
}
}
}
(3)现在来看看EmbeddedServletContainerCustomizerBeanPostProcessor做了什么,详见下面的代码注释。
// 实现了两个接口BeanPostProcessor和BeanFactoryAware
// BeanPostProcessor主要用于bean在初始化前后可以定制
// BeanFactoryAware的setBeanFactory可以用来获取beanFactory
public class EmbeddedServletContainerCustomizerBeanPostProcessor
implements BeanPostProcessor, BeanFactoryAware {
private ListableBeanFactory beanFactory;
private List customizers;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
"EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "
+ "with a ListableBeanFactory");
this.beanFactory = (ListableBeanFactory) beanFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// 如果bean是ConfigurableEmbeddedServletContainer则执行,巧的很,我们上一步在
// EmbeddedServletContainerAutoConfiguration中实例化的TomcatEmbeddedServletContainerFactory就属于ConfigurableEmbeddedServletContainer,所以postProcessBeforeInitialization方法对TomcatEmbeddedServletContainerFactory起作用
if (bean instanceof ConfigurableEmbeddedServletContainer) {
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
// 找到所有的EmbeddedServletContainerCustomizer实例,然后分别执行他们的customize方法
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean); //这里就引出了一个问题,这些customizer是哪里来的
}
}
private Collection getCustomizers() {
if (this.customizers == null) {
// 找到所有的EmbeddedServletContainerCustomizer实例
// Look up does not include the parent context
this.customizers = new ArrayList(
this.beanFactory
.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false)
.values());
//对customizers下的实例就行排序,排序的规则主要是依据实例是否有@Order注解或实现的Order接口,根据order值进行排序
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
}
(4) 根据上面的代码分析,我们发现它用到了多个EmbeddedServletContainerCustomizer实例,那么这些实例是哪里来的呢?从类名的语义来看好像是用来自定义EmbeddedServletContainer的,先看一下它的接口代码,它只有一个customize方法,而入参是ConfigurableEmbeddedServletContainer,而我们之前在步骤(2)中实例化的TomcatEmbeddedServletContainerFactory也是他的一个实现类。
public interface EmbeddedServletContainerCustomizer {
/**
* Customize the specified {@link ConfigurableEmbeddedServletContainer}.
* @param container the container to customize
*/
void customize(ConfigurableEmbeddedServletContainer container);
}
既然它是个可自定义实现的接口,那就说明我们可以根据实现EmbeddedServletContainerCustomizer接口来对TomcatEmbeddedServletContainerFactory进行定制化配置。比如我们需要在tomcat中添加一个自定义的valve,显然通过修改properties是没办法直接实现的,因此需要我们通过代码的方式来解决这个问题。
@Bean
public EmbeddedServletContainerCustomizer createEmbeddedServletContainerFactory() {
return container -> {
TomcatEmbeddedServletContainerFactory factory = (TomcatEmbeddedServletContainerFactory) container;
factory.addContextValves(valve);
};
}
刚才我们也说到,EmbeddedServletContainerCustomizerBeanPostProcessor有多个customizer,那还有哪些呢?以下是启动spring-boot时的断点,可以发现有5个customizer,其中最后一个是我自定义的,其他都是spring-boot默认实现的,大家有没有注意到其中有个ServerProperties,熟悉spring-boot配置的对这个类应该不陌生,里面对应的是application.yml中的server.port 、server.address、server.contextPath等配置,所以这也解释了为什么我们可以在applicaiton.yml对内嵌的tomcat进行配置了。
一般情况下,我们看到ServerProperties的名字首先想到的它就是一个配置类,然而看一下它的结构,他却实现了很多接口,比如我们上面提到的EmbeddedServletContainerCustomizer,还有Orderd接口,所以他在上面排序其实是跟他实现的Orderd有关的(这其实是一种很奇怪的实现,所以在2.0版本以后做了改变)
(5) 上面的过程其实很简单,就是实例化一个TomcatEmbeddedServletContainerFactory实例,然后在EmbeddedServletContainerCustomizerBeanPostProcessor中使用多个EmbeddedServletContainerCustomizer对TomcatEmbeddedServletContainerFactory进行修改,最终得到一个可用的TomcatEmbeddedServletContainerFactory,而通过这个factory的getEmbeddedServletContainer方法可以获得一个TomcatEmbeddedServletContainer,至此tomcat的实例化讲解完毕。
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
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 getTomcatEmbeddedServletContainer(tomcat);
}
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}
最后再补充一点儿,我们知道spring-boot默认支持三种web服务:tomcat、jetty、undertow ,为什么默认是tomcat,怎样可以使用其他两种,我们先回答第一个问题,在之前的EmbeddedServletContainerAutoConfiguration中有这样一段代码,由于jetty和undertow依赖的java类不在classpath中,所以不会实例化对应的factory,另外还有个@ConditionalOnMissingBean限制条件,只有EmbeddedServletContainerFactory实例不存在时才会加载jetty或undertow,这其实也引出了第二个问题的答案...
以jetty为例,我们怎样才能让spring-boot使用jetty作为默认的web容器呢?只需要做两件事:加入jetty的相关依赖包,剔除tomcat的相关依赖包,在maven中我们可以这样做
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
org.springframework.boot
spring-boot-starter-jetty
有兴趣的同学还可以参考另一篇博文 spring-boot如何得到一个tomcat实例(基于spring-boot_v2.0.6.RELEASE)