spring-boot如何得到一个tomcat实例(基于spring-boot_v1.5.14.RELEASE)

       我们知道,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进行配置了。

     spring-boot如何得到一个tomcat实例(基于spring-boot_v1.5.14.RELEASE)_第1张图片

        一般情况下,我们看到ServerProperties的名字首先想到的它就是一个配置类,然而看一下它的结构,他却实现了很多接口,比如我们上面提到的EmbeddedServletContainerCustomizer,还有Orderd接口,所以他在上面排序其实是跟他实现的Orderd有关的(这其实是一种很奇怪的实现,所以在2.0版本以后做了改变)

spring-boot如何得到一个tomcat实例(基于spring-boot_v1.5.14.RELEASE)_第2张图片

      (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,这其实也引出了第二个问题的答案...

spring-boot如何得到一个tomcat实例(基于spring-boot_v1.5.14.RELEASE)_第3张图片

      以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)

你可能感兴趣的:(spring-boot)