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方法調用一遍。這樣就可以使自定義的配置生效。

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