spring-boot定制内置tomcat

       我们知道,springboot默认使用的是内嵌的tomcat作为web服务器,我们可以通过配置文件对tomcat的参数进行修改,比如server.port表示监听端口,server.contextPath表示上下文路径,server.tomcat.max-connections表示,完整配置可参考 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html

      但是有时候默认的配置并不能解决所有问题,比如我们需要在tomcat中添加一个自定义的valve,显然通过修改properties是没办法直接实现的,因此需要我们通过代码的方式来解决这个问题。一般有两种比较常见的方式,下面逐一介绍

       方法一:  自定义实现一个EmbeddedServletContainerCustomizer

  @Bean
  public EmbeddedServletContainerCustomizer createEmbeddedServletContainerFactory() {
    return container -> {
      TomcatEmbeddedServletContainerFactory factory =   (TomcatEmbeddedServletContainerFactory) container;
      factory.addContextValves(valve);
    };
  }
 

   方法二:  实例化一个TomcatEmbeddedServletContainerFactory

  @Bean
  public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory(@Autowired StuckThreadDetectionValve valve) {
    TomcatEmbeddedServletContainerFactory factory= new TomcatEmbeddedServletContainerFactory();
    factory.addContextValves(valve);
    return factory ;
  }

     通过代码我们可以发现,他们最终都是对TomcatEmbeddedServletContainerFactory进行修改,所以他们的最终的目的是一样的。但是他们也有比较显著的区别,可以参考代码

  org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor

 注意下面这段代码

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
    //实例化的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方法,而这个方法的最终
     //目地是对它的入参进行定制化修改,而这时的入参就是TomcatEmbeddedServletContainerFactory
     for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
		customizer.customize(bean);
	 }
    //这个for循环结束后,其实是对TomcatEmbeddedServletContainerFactory进行了一次重置

}

//这里会找到所有的EmbeddedServletContainerCustomizer
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
	if (this.customizers == null) {
	   // Look up does not include the parent context
	   this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(         this.applicationContext.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false).values());
        //注意,这里对找到的EmbeddedServletContainerCustomizer进行了一次排序
		Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
		this.customizers = Collections.unmodifiableList(this.customizers);
		}
		return this.customizers;
	}

      在代码注释中,我们强调了getCustomizers()方法会对获取到的所有EmbeddedServletContainerCustomizer进行一次排序,具体的排序原理这里先不谈,如果我们自定义类一个EmbeddedServletContainerCustomizer,最终有5个,否则是4个.

这其中有3个我们是不需要关注的:0- TomcatWebSocketContainerCustomizer ,1-LocaleCharsetMappingsCustomizer,3-DuplicateServerPropertiesDetector ,有兴趣的同学可以自己研究一下,这里不赘述。

我们关注的是 2-ServerProperties 和最后一个(这是我自定义的一个EmbeddedServletContainerCustomizer) 。可以发现排序的最终结果是让ServerProperties在我们自定义的Customizer之前。为什么要这样做,因为在上面的代码中需要以此进行customize

 for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
        customizer.customize(bean);
 }

我们先看一下ServerProperties的customize方法做了什么

从代码中,我们看到它在对传入的container进行设置,而设置的依据是来自ServerProperties的配置(也是我们上面提到的common-application-properties.html 中的server.*.*中的配置) , 从这里我们可以得出另一个结论,我们使用自定义Customizer时,server.*中的配置还是可以正常使用的。但是自定义的Customizer可以覆盖server.*中的配置,而自定义的TomcatEmbeddedServletContainerFactory不会覆盖server.*中的配置,这个我们在最后的总结中会提到

     我们之前说要参考EmbeddedServletContainerCustomizerBeanPostProcessor的代码,那么这个类又是如何被调用的呢?看一下代码 org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)  //这里引入了BeanPostProcessorsRegistrar
public class EmbeddedServletContainerAutoConfiguration {

//这里省略部分代码


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

 @Bean
 public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
      return new TomcatEmbeddedServletContainerFactory();
    }

  }

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
	if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(
					EmbeddedServletContainerCustomizerBeanPostProcessor.class, true,
					false))) {
				registry.registerBeanDefinition(
						"embeddedServletContainerCustomizerBeanPostProcessor",
						new RootBeanDefinition(
								EmbeddedServletContainerCustomizerBeanPostProcessor.class));

			}
	//其余代码省略		

    EmbeddedServletContainerAutoConfiguration使用@Import注解引入了 BeanPostProcessorsRegistrar ,同时如果没有找到TomcatEmbeddedServletContainerFactory的实例化Bean,则会创建一个。BeanPostProcessorsRegistrar引入了EmbeddedServletContainerCustomizerBeanPostProcessor。因此整个流程就串起来了。

       上面零零散散的说的比较杂乱,这里通过描述tomcat的加载过程总结一下:

       (1) springboot的主函数有一个注解 @SpringBootApplication,而这个注解里有一个@EnableAutoConfiguration,所有的故事就从这里开始吧。

      (2) EnableAutoConfiguration会触发许多自动配置,其中就包括了EmbeddedServletContainerAutoConfiguration

      (3) EmbeddedServletContainerAutoConfiguration首先会做一件事,实例化TomcatEmbeddedServletContainerFactory,如果我们没有自己实例化Factory,则会创建一个,最终的结果是要在applicationContext中实例化一个TomcatEmbeddedServletContainerFactory,我们接下来的工作都是围绕它来说的,为了简单,我们暂时简称它为Factory-Bean 

      (4) 接下来EmbeddedServletContainerAutoConfiguration还会做一件事,通过@Import引入BeanPostProcessorsRegistrar,而Registar通过registerBeanDefinitions()方法又实例化了EmbeddedServletContainerCustomizerBeanPostProcessor。

      (5)现在来看看EmbeddedServletContainerCustomizerBeanPostProcessor做了什么,首先它找到Factory-Bean,然后再找到所有的EmbeddedServletContainerCustomizer实例(所有的实例已排序),然后对所有的Customizer执行customize()方法,而customize()方法会对Factory-Bean进行设置,因为其中的一个customizer是ServerProperties,因此ServerProperties中的配置都会设置到Factory-Bean,所以说我们如果是自定义的Factory-Bean,如果你在自定义的时候设置过port等值,再这一步还是会被覆盖,然而如果我们使用的是自定义的Customizer,自定义时设置的port或者其他值就不会被覆盖,因为ServerProperties的customize()在我们自定义的Customizer之前执行(因为有排序,自定义的Customizer一定在SystemProperties之后)。

      (6)EmbeddedServletContainerCustomizerBeanPostProcessor运行之后就得到了一个配置好的Factory-Bean,而Factory-Bean通过它的getEmbeddedServletContainer()方法得到一个TomcatEmbeddedServletContainer,我们最终的结果不就是想要一个可用的tomcat吗?所以故事讲到这里就可以了。

      最后,我们在总结一下这两种方法的区别

方法一:  自定义实现一个EmbeddedServletContainerCustomizer

    看名字就知道它是为自定义而生的,所以用它肯定没错,但是我个人不是很喜欢用它,因为它会覆盖ServerProperties中的值,ServerProperties是可以通过配置文件或环境变量进行配置的,既然可以配置,我还再做一次定制干吗呢?因为你最终定制的结果还不是为了可灵活配置吗,既然ServerProperties已经做到了,那就不需要多此一举了。

方法二:  实例化一个TomcatEmbeddedServletContainerFactory

    自定义ContainerFactory可以定制ServerProperties中没有的配置,但是一定要比较清楚它的原理,比如我之前想在这里面设置background-processor-delay,最后才发现被ServerProperties覆盖了。

  总的来说,这两种方式几乎是一样的效果,只要在使用时理解他们的原理就可以了.

猜你喜欢

转载自blog.csdn.net/song_java/article/details/81190449