spring boot原理分析(四):项目依赖包中bean的自动配置2之tomcat属性配置

spring boot原理分析(四):项目依赖包中bean的自动配置2之tomcat属性配置

前言

    原理分析(三)因为偷…额,为了保持结构上的独立性,只介绍了spring boot如何加载了自动配置类,并没有分析自动配置类加载bean和配置环境的原理。这个点才是spring boot改进tomcat + spring mvc框架繁琐配置问题的最终关键所在。不过,由于自动配置类过多,不能一一列举,所以这里只会将比较典型而且对分析后面代码有意义的配置类拉出来分析。

Servlet容器–Tomcat的配置

自动配置类

    这里首先选取ServletWebServerFactoryAutoConfiguration自动配置类进行分析。在原理分析(一)已经提到过,spring boot支持三种类型的Servlet容器,分别是Tomcat、Jetty和Undertow,所以在应用初始化时,就需要设置相应的容器环境,为容器的启动做准备。这个类的作用就是配置Servlet容器环境。

@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);
	}

  //内部静态类,注册WebServerFactoryCustomizerBeanPostProcessor
	public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

		private ConfigurableListableBeanFactory beanFactory;

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

    //注册相关的BeanPostProcessor
		@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. @Configuration声明这个类是java配置类;
  2. @AutoConfigureOrder设定了配置的加载优先级,HIGHEST_PRECEDENCE代表最高优先级,而LOWEST_PRECEDENCE代表最低优先级;
  3. @ConditionalOnClass(ServletRequest.class)注明这个配置的加载需要基于ServletRequest已经加载的条件下,如果了解了tomcat和spring mvc的原理,就知道ServletRequest是Servlet容器的传递的请求模型;
  4. @ConditionalOnWebApplication(type = Type.SERVLET)注明这个配置的加载需要当前项目是基于Servlet的web项目,这个注解其他可选的参数有,ANY——代表任何web项目,REACTIVE——代表REACTIVE web项目;
  5. @EnableConfigurationProperties(ServerProperties.class)注明允许注入ServerProperties的bean,并且支持properties和yml文件配置属性,这个后面会细讲;
  6. 剩下的
@Import({
  ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
  ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
  ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class
})

就比较明显了,注入以上几个类型的bean,其中BeanPostProcessorsRegistrar和EmbeddedTomcat是这篇文章主要关注的,其中当满足

@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })

时,EmbeddedTomcat中会注入TomcatServletWebServerFactory的bean。嵌入的Jetty和Undertow当然也可以作为Servlet容器,分别需要满足

@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })

才会被注入。关于这个两者的研究分析暂时不是我的菜。

@Configuration
class ServletWebServerFactoryConfiguration {

	@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();
		}

	}
	......
}

    类内部最先会被加载的是内部静态类,上面@import也注入了BeanPostProcessorsRegistrar。BeanPostProcessorsRegistrar实现自ImportBeanDefinitionRegistrar和BeanFactoryAware。BeanFactoryAware这个接口的唯一方法setBeanFactory(BeanFactory beanFactory)会被自动调用,传入的BeanFactory是创建bean的Factory,特定类型的工厂除了可以用来查找bean,还可以用来查看bean的属性、销毁bean等。这里是获取了ConfigurableListableBeanFactory的bean,并判断了指定类型的bean是否存在,如果不存在才需要注入。在原理分析三中,已经介绍过ImportBeanDefinitionRegistrar,

这个接口的实现类可以由@Import注解引入,会自动调用它的registerBeanDefinitions方法。registerBeanDefinitions方法传入了用来获取注解信息的AnnotationMetadata和用来注册bean的BeanDefinitionRegistry。ImportBeanDefinitionRegistrar这个接口可以用来注入bean或者实现自己定义的类@Component注解。

    这里的作用是注册WebServerFactoryCustomizerBeanPostProcessor和ErrorPageRegistrarBeanPostProcessor。BeanPostProcessor接口可以在bean的注入前和注入后对bean进行相应的操作,其中postProcessBeforeInitialization和postProcessAfterInitialization方法会被框架自动调用。WebServerFactoryCustomizerBeanPostProcessor和ErrorPageRegistrarBeanPostProcessor是这个接口的两个具体实现,分别对应处理WebServerFactoryCustomizer和ErrorPageRegistrar的bean。在这里有一点奇怪的是,为什么错误页面注册(ErrorPageRegistrar)相关的要放在这里,这属于spring mvc应该负责的工作,我的理解应该在放在WebMvcAutoConfiguration的配置。这篇文章不对ErrorPageRegistrarBeanPostProcessor做过多分析。

Servlet容器配置类

    ServletWebServerFactoryAutoConfiguration中定义了两个bean,ServletWebServerFactoryCustomizer和TomcatServletWebServerFactoryCustomizer。ServletWebServerFactoryCustomizer是Servlet容器的统一配置,TomcatServletWebServerFactoryCustomizer是具体到Tomcat容器的配置类。从这里也可以确认Tomcat是spring boot的默认的servlet容器,因为这里只有tomcat的配置。
    在这两个bean的构造参数中都用到了ServerProperties,这是在上文@EnableConfigurationProperties(ServerProperties.class)中注入的。

//属性配置的前缀为server
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

	 //Server HTTP 端口设置.
	private Integer port;

	 //绑定的网络地址.
	private InetAddress address;

	//错误配置相关,比如配置错误的url路径,比如哪些错误需要被处理
	@NestedConfigurationProperty
	private final ErrorProperties error = new ErrorProperties();

	 //定义X-Forwarded扩展头部是否需要应用
	private Boolean useForwardHeaders;

	 //定义的Server的response相关的header,如果为空则不发送任何
	private String serverHeader;


	 //定义最大http消息头的大小
	private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8);

	//定义连接超时,如果为-1代表没有超时设置
	private Duration connectionTimeout;

	//ssl配置
	@NestedConfigurationProperty
	private Ssl ssl;

	//压缩配置
	@NestedConfigurationProperty
	private final Compression compression = new Compression();

	//http2配置
	@NestedConfigurationProperty
	private final Http2 http2 = new Http2();

	//默认的Servlet配置,具体配置字段可以参考后面
	private final Servlet servlet = new Servlet();

	//新建tomcat容器
	private final Tomcat tomcat = new Tomcat();

	//新建jetty容器
	private final Jetty jetty = new Jetty();

	//新建Undertow容器
	private final Undertow undertow = new Undertow();
	......(一些getter和setter)

	/**
	 * Servlet属性.
	 */
	public static class Servlet {

		 //Servlet环境配置中的初始化的参数
		private final Map<String, String> contextParameters = new HashMap<>();

		 //Servlet环境配置文件的路径
		private String contextPath;

		 //应用名,默认为"application"
		private String applicationDisplayName = "application";

		//jsp配置
		@NestedConfigurationProperty
		private final Jsp jsp = new Jsp();

		//会话session配置
		@NestedConfigurationProperty
		private final Session session = new Session();
		......(一些getter和setter)

	}

	/**
	 * Tomcat 属性配置.
	 */
	public static class Tomcat {

		 //access log 配置
		private final Accesslog accesslog = new Accesslog();

		/**
		 * Regular expression that matches proxies that are to be trusted.
		 */
		 //内部代理?不太懂,看起来是ipv4和ipv6的局域网网段ip
		private String internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8
				+ "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16
				+ "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16
				+ "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 127/8
				+ "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12
				+ "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|" //
				+ "0:0:0:0:0:0:0:1|::1";

		 //记录传入的协议头,通常是X-Forwarded-Proto
		private String protocolHeader;

		 //协议头,表明是否使用ssl
		private String protocolHeaderHttpsValue = "https";

		 //协议头用来覆盖原有的端口
		private String portHeader = "X-Forwarded-Port";

		 //远程IP的协议头,比如“`X-FORWARDED-FOR`”
		private String remoteIpHeader;

		 //tomcat基础目录,如果没有定义,则会使用一个临时目录
		private File basedir;

		 //backgroundProcess的周期时间设置
		@DurationUnit(ChronoUnit.SECONDS)
		private Duration backgroundProcessorDelay = Duration.ofSeconds(10);

		 //工人线程(worker thread)的上限
		private int maxThreads = 200;

		//工人线程(worker thread)的下限
		private int minSpareThreads = 10;

		 //HTTP post内容大小的上限
		private DataSize maxHttpPostSize = DataSize.ofMegabytes(2);

		 //HTTP消息头大小的上限
		private DataSize maxHttpHeaderSize = DataSize.ofBytes(0);

		 //接收的请求体的大小上限
		private DataSize maxSwallowSize = DataSize.ofMegabytes(2);

		 //设置请求的路径是否需要加"/"实现重定向
		private Boolean redirectContextRoot = true;

		 //Http1.1或者更高版本的协议是否需要使用相对或者
		 //绝对路径的重定向
		private Boolean useRelativeRedirects;

		 //编码
		private Charset uriEncoding = StandardCharsets.UTF_8;

		 //server最大能够接收和处理的连接数。
		 //如果达到这个上限,系统还是会依据acceptCount属性
		 //设置的大小来建立连接
		private int maxConnections = 10000;

		 //当所有线程都被占用后,接收请求的最大队列长度
		private int acceptCount = 100;

		private List<String> additionalTldSkipPatterns = new ArrayList<>();

		 //静态资源的配置,具体字段可以参考后面
		private final Resource resource = new Resource();
		......(一些getter和setter)

		 //Tomcat access log 的配置属性.
		public static class Accesslog {

			 // 是否输出 access log,默认不输出.
			private boolean enabled = false;
			......
		}

		 // Tomcat 静态资源属性配置.
		public static class Resource {

			 //web应用的静态资源是否允许缓存.
			private boolean allowCaching = true;

			 // 静态资源缓存的存活时间.
			private Duration cacheTtl;
			......

		}

	}

	// Jetty 配置.
	public static class Jetty {
		......
	}

	//Undertow 配置.
	public static class Undertow {
		......
	}

}

ServerProperties中的大多数的字段我都在上面代码中注释了,这些字段都可以在properties文件或者yml文件里配置,很多默认还关乎tomcat和servlet的运行,所以建议大家还是大致浏览一遍。
    ServletWebServerFactoryCustomizer和TomcatServletWebServerFactoryCustomizer的代码比较类似,首先这两个类都继承自WebServerFactoryCustomizer接口,这个接口只有customize方法,后面会说到何时被调用。这两个类继承了Ordered接口,这个接口同样的只有一个方法getOrder,返回的整型可以代表实现类加载的优先级。这两个类的构造方法的内部逻辑也很简单,就直接将传入的ServerProperties引用赋值给了内部持有的ServerProperties引用。

//ServletWebServerFactoryCustomizer
public class ServletWebServerFactoryCustomizer
		implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {

	private final ServerProperties serverProperties;

	public ServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
		this.serverProperties = serverProperties;
	}

	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public void customize(ConfigurableServletWebServerFactory factory) {
		......
	}

}
//TomcatServletWebServerFactoryCustomizer
public class TomcatServletWebServerFactoryCustomizer
		implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {

	private final ServerProperties serverProperties;

	public TomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
		this.serverProperties = serverProperties;
	}

	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public void customize(TomcatServletWebServerFactory factory) {
		......
	}
}

所以最重要的逻辑是在customize方法里。customize方法传入了ServletWebServerFactory,然后将serverProperties中设置的值赋值给ServletWebServerFactory,包括tomcat配置等等。下面需要稍稍插入一点内容,来说明customize方法是如何被调用的。

Servlet容器工厂注入前后

    上文中提到注册了WebServerFactoryCustomizerBeanPostProcessor的bean,并没有讲这个类的具体作用,但是讲了这个类的父类BeanPostProcessor的原理。下面就对这个类进行分析,介绍它是如何与customize方法配合使用的。

public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {

	private ListableBeanFactory beanFactory;

	private List<WebServerFactoryCustomizer<?>> customizers;

  //获取BeanFactory
	@Override
	public void setBeanFactory(BeanFactory beanFactory) {
		Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
				"WebServerCustomizerBeanPostProcessor can only be used " + "with a ListableBeanFactory");
		this.beanFactory = (ListableBeanFactory) beanFactory;
	}

  //注入WebServerFactory bean之前
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (bean instanceof WebServerFactory) {
			postProcessBeforeInitialization((WebServerFactory) bean);
		}
		return bean;
	}

  //注入WebServerFactory bean之后
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@SuppressWarnings("unchecked")
	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) {
			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();
	}

}

    该类实现自BeanFactoryAware接口,依旧利用了setBeanFactory方法的BeanFactory获取功能,并将ListableBeanFactory实例赋给了自身持有的BeanFactory类型的引用。ListableBeanFactory能够将同类型(包括同父类)的bean全部枚举出来。在getCustomizers方法中使用了这个bean,将所有WebServerFactoryCustomizer子类的bean都筛选了出来。
    注入WebServerFactory类之前,postProcessBeforeInitialization利用"回调",执行了所有WebServerFactoryCustomizer的customize方法,具体的实现原理可以参考"回调""和spring的LambdaSafe类。注入WebServerFactory之后,postProcessAfterInitialization方法中没有实质性的代码逻辑,说明没有特殊处理。

配置WebServerFactory

    这样ServletWebServerFactoryCustomizer和TomcatServletWebServerFactoryCustomizer的customize方法都会被调用。下面就来具体分析一下这两个类的customize方法。

//ServletWebServerFactoryCustomizer
public class ServletWebServerFactoryCustomizer
		implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
	......
	@Override
	public void customize(ConfigurableServletWebServerFactory factory) {
		PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
		map.from(this.serverProperties::getPort).to(factory::setPort);
		map.from(this.serverProperties::getAddress).to(factory::setAddress);
		map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
		map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
		map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
		map.from(this.serverProperties::getSsl).to(factory::setSsl);
		map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
		map.from(this.serverProperties::getCompression).to(factory::setCompression);
		map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
		map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
		map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
	}
    ......
}
//TomcatServletWebServerFactoryCustomizer
public class TomcatServletWebServerFactoryCustomizer
		implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {

	private final ServerProperties serverProperties;
	......

	@Override
	public void customize(TomcatServletWebServerFactory factory) {
		ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
		if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
			factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());
		}
		if (tomcatProperties.getRedirectContextRoot() != null) {
			customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot());
		}
		if (tomcatProperties.getUseRelativeRedirects() != null) {
			customizeUseRelativeRedirects(factory, tomcatProperties.getUseRelativeRedirects());
		}
	}

	private void customizeRedirectContextRoot(ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) {
		factory.addContextCustomizers((context) -> context.setMapperContextRootRedirectEnabled(redirectContextRoot));
	}

	private void customizeUseRelativeRedirects(ConfigurableTomcatWebServerFactory factory,
			boolean useRelativeRedirects) {
		factory.addContextCustomizers((context) -> context.setUseRelativeRedirects(useRelativeRedirects));
	}

}

    ServletWebServerFactoryCustomizer的customize只是使用PropertyMapper将serverProperties的数据拷贝到了ServletWebServerFactory的配置中。这个里面PropertyMapper是比较有意思的工具,可以研究一下原理。
    TomcatServletWebServerFactoryCustomizer配置了TomcatServletWebServerFactory的参数属性,拷贝了上面ServerProperties中的属性到TomcatServletWebServerFactory中。所以对于没有自行设置的属性,ServerProperties配置的默认属性就会在tomcat运行中发挥作用。在研究项目运行性能的时候,需要关注这些默认的属性,比如线程池配置。

附:
    如果只是为了简单实现属性的配置,并不需要这么复杂。spring boot这样复杂处理的意义在于,能够支持灵活的拓展,开发者可以通过实现WebServerFactoryCustomizer接口,可以自定义WebServerFactory的属性配置逻辑。然后WebServerFactoryCustomizerBeanPostProcessor会把所有的WebServerFactoryCustomizer实例的customize方法调用一遍。这样就可以使自定义的配置生效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章