嵌入式Servlet容器自动配置启动自定义配置原理

Posted 恒奇恒毅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式Servlet容器自动配置启动自定义配置原理相关的知识,希望对你有一定的参考价值。

SpringBoot中提供了自动配置功能,嵌入式Servlet容器也是通过自动配置完成配置的。默认使用tomcat。我们可以通过starter-web的依赖窥见。

一、EmbeddedServletContainerAutoConfiguration

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration 

	/**
	 * Nested configuration if Tomcat is being used.
	 */
	@Configuration
	@ConditionalOnClass( Servlet.class, Tomcat.class )
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat 

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

	
	@Configuration
	@ConditionalOnClass( Servlet.class, Server.class, Loader.class,
			WebAppContext.class )
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedJetty 

		@Bean
		public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() 
			return new JettyEmbeddedServletContainerFactory();
		

	
	@Configuration
	@ConditionalOnClass( Servlet.class, Undertow.class, SslClientAuthMode.class )
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedUndertow 

		@Bean
		public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() 
			return new UndertowEmbeddedServletContainerFactory();
		

	
  • 当满足web环境@ConditionalOnWebApplication的时候,该自动配置类生效,在内部定义了静态内部类EmbeddedTomcatEmbeddedUndertowEmbeddedJetty,即SpringBoot默认支持三种嵌入式Servlet容器,分别在满足的时候自动配置。他们三者都是在容器中没有EmbeddedServletContainerFactory的时候才配置,说明最多只会装配一种容器。以Tomcat为例,它往容器中添加了一个Bean(TomcatEmbeddedServletContainerFactory),该类实现了EmbeddedServletContainerFactory,它用于创建Servlet容器EmbeddedServletContainerEmbeddedServletContainerFactory的作用后面再表。
    如果我们想换一种容器,我们简单地排除tomcat依赖,引入其容器依赖即可。例如Jetty。
<dependency> 
	<groupId>org.springframework.boot</groupId> 
	<artifactId>spring‐boot‐starter‐web</artifactId> 
	<exclusions> 
		<exclusion> 
			<artifactId>spring‐boot‐starter‐tomcat</artifactId> 
			<groupId>org.springframework.boot</groupId> 
		</exclusion> 
	</exclusions> 
</dependency> 
<!‐‐引入其他的Servlet容器‐‐> 
<dependency> 
	<artifactId>spring‐boot‐starter‐jetty</artifactId> 
	<groupId>org.springframework.boot</groupId> 
</dependency>
public interface EmbeddedServletContainerFactory 

	/**
	 * Gets a new fully configured but paused @link EmbeddedServletContainer instance.
	 * Clients should not be able to connect to the returned server until
	 * @link EmbeddedServletContainer#start() is called (which happens when the
	 * @link ApplicationContext has been fully refreshed).
	 * @param initializers @link ServletContextInitializers that should be applied as
	 * the container starts
	 * @return a fully configured and started @link EmbeddedServletContainer
	 * @see EmbeddedServletContainer#stop()
	 */
	EmbeddedServletContainer getEmbeddedServletContainer(
			ServletContextInitializer... initializers);


  • EmbeddedServletContainerAutoConfiguration还通过@Import导入了BeanPostProcessorsRegistrar。它往容器中导入了两个Bean:EmbeddedServletContainerCustomizerBeanPostProcessorErrorPageRegistrarBeanPostProcessor
	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;
			
			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);
			
		

	
  • EmbeddedServletContainerCustomizerBeanPostProcessor提供了对嵌入式容器自定义配置的功能,对ConfigurableEmbeddedServletContainer这种类型的Bean进行定制化,而具体的容器工厂类TomcatEmbeddedServletContainerFactory正好就是ConfigurableEmbeddedServletContainer
  • 具体是从容器中获取所有的EmbeddedServletContainerCustomizer,然后一一调用完成定制化。所以如果我们想定制容器,我们可以往容器中放入EmbeddedServletContainerCustomizer即可。
public class EmbeddedServletContainerCustomizerBeanPostProcessor
		implements BeanPostProcessor, BeanFactoryAware 

	private ListableBeanFactory beanFactory;

	private List<EmbeddedServletContainerCustomizer> 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 
		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) 
		for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) 
			customizer.customize(bean);
		
	

	private Collection<EmbeddedServletContainerCustomizer> getCustomizers() 
		if (this.customizers == null) 
			// Look up does not include the parent context
			this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
					this.beanFactory
							.getBeansOfType(EmbeddedServletContainerCustomizer.class,
									false, false)
							.values());
			Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
			this.customizers = Collections.unmodifiableList(this.customizers);
		
		return this.customizers;
	



ConfigurableEmbeddedServletContainer提供了如下配置项

public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry 

	/**
	 * Sets the context path for the embedded servlet container. The context should start
	 * with a "/" character but not end with a "/" character. The default context path can
	 * be specified using an empty string.
	 * @param contextPath the contextPath to set
	 */
	void setContextPath(String contextPath);

	/**
	 * Sets the display name of the application deployed in the embedded servlet
	 * container.
	 * @param displayName the displayName to set
	 * @since 1.3.0
	 */
	void setDisplayName(String displayName);

	/**
	 * Sets the port that the embedded servlet container should listen on. If not
	 * specified port '8080' will be used. Use port -1 to disable auto-start (i.e start
	 * the web application context but not have it listen to any port).
	 * @param port the port to set
	 */
	void setPort(int port);

	/**
	 * The session timeout in seconds (default 30 minutes). If 0 or negative then sessions
	 * never expire.
	 * @param sessionTimeout the session timeout
	 */
	void setSessionTimeout(int sessionTimeout);

	/**
	 * The session timeout in the specified @link TimeUnit (default 30 minutes). If 0 or
	 * negative then sessions never expire.
	 * @param sessionTimeout the session timeout
	 * @param timeUnit the time unit
	 */
	void setSessionTimeout(int sessionTimeout, TimeUnit timeUnit);

	/**
	 * Sets if session data should be persisted between restarts.
	 * @param persistSession @code true if session data should be persisted
	 */
	void setPersistSession(boolean persistSession);

	/**
	 * Set the directory used to store serialized session data.
	 * @param sessionStoreDir the directory or @code null to use a default location.
	 */
	void setSessionStoreDir(File sessionStoreDir);

	/**
	 * Sets the specific network address that the server should bind to.
	 * @param address the address to set (defaults to @code null)
	 */
	void setAddress(InetAddress address);

	/**
	 * Set if the DefaultServlet should be registered. Defaults to @code true so that
	 * files from the @link #setDocumentRoot(File) document root will be served.
	 * @param registerDefaultServlet if the default servlet should be registered
	 */
	void setRegisterDefaultServlet(boolean registerDefaultServlet);

	/**
	 * Sets the error pages that will be used when handling exceptions.
	 * @param errorPages the error pages
	 */
	void setErrorPages(Set<? extends ErrorPage> errorPages);

	/**
	 * Sets the mime-type mappings.
	 * @param mimeMappings the mime type mappings (defaults to
	 * @link MimeMappings#DEFAULT)
	 */
	void setMimeMappings(MimeMappings mimeMappings);

	/**
	 * Sets the document root directory which will be used by the web context to serve
	 * static files.
	 * @param documentRoot the document root or @code null if not required
	 */
	void setDocumentRoot(File documentRoot);

	/**
	 * Sets @link ServletContextInitializer that should be applied in addition to
	 * @link EmbeddedServletContainerFactory#getEmbeddedServletContainer(ServletContextInitializer...)
	 * parameters. This method will replace any previously set or added initializers.
	 * @param initializers the initializers to set
	 * @see #addInitializers
	 */
	void setInitializers(List<? extends ServletContextInitializer> initializers);

	/**
	 * Add @link ServletContextInitializers to those that should be applied in addition
	 * to
	 * @link EmbeddedServletContainerFactory#getEmbeddedServletContainer(ServletContextInitializer...)
	 * parameters.
	 * @param initializers the initializers to add
	 * @see #setInitializers
	 */
	void addInitializers(ServletContextInitializer... initializers);

	/**
	 * Sets the SSL configuration that will be applied to the container's default
	 * connector.
	 * @param ssl the SSL configuration
	 */
	void setSsl(Ssl ssl);

	/**
	 * Sets a provider that will be used to obtain SSL stores.
	 * @param sslStoreProvider the SSL store provider
	 */
	void setSslStoreProvider(SslStoreProvider sslStoreProvider);

	/**
	 * Sets the configuration that will be applied to the container's JSP servlet.
	 * @param jspServlet the JSP servlet configuration
	 */
	void setJspServlet(JspServlet jspServlet);

	/**
	 * Sets the compression configuration that will be applied to the container's default
	 * connector.
	 * @param compression the compression configuration
	 */
	void setCompression(Compression compression);

	/**
	 * Sets the server header value.
	 * @param serverHeader the server header value
	 */
	void setServerHeader(String serverHeader);

	/**
	 * Sets the Locale to Charset mappings.
	 * @param localeCharsetMappings the Locale to Charset mappings
	 */
	void setLocaleCharsetMappings(Map<Locale, Charset> localeCharsetMappings);


  • ErrorPageRegistrarBeanPostProcessor提供错误页面配置的功能,针对ErrorPageRegistry,从容器中获取所有的ErrorPageRegistrar,然后一一调用registerErrorPages。所以如果我们想注册error page,我们可以往容器中放入ErrorPageRegistrar即可。
public class ErrorPageRegistrarBeanPostProcessor
		implements BeanPostProcessor, BeanFactoryAware 

	private ListableBeanFactory beanFactory;

	private List<ErrorPageRegistrar> registrars;

	@Override
	public void setBeanFactory(BeanFactory beanFactory) 
		Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
				"ErrorPageRegistrarBeanPostProcessor can only be used "
						+ "with a ListableBeanFactory");
		this.beanFactory = (ListableBeanFactory) beanFactory;
	

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException 
		if (bean instanceof ErrorPageRegistry) 
			postProcessBeforeInitialization((ErrorPageRegistry) bean);
		
		return bean;
	

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException 
		return bean;
	

	private void postProcessBeforeInitialization(ErrorPageRegistry registry) 
		for (ErrorPageRegistrar registrar : getRegistrars()) 
			registrar.registerErrorPages(registry);
		
	

	private Collection<ErrorPageRegistrar> getRegistrars() 
		if (this.registrars == null) 
			// Look up does not include the parent context
			this.registrars = new ArrayList<ErrorPageRegistrar>(this.beanFactory
					.getBeansOfType(ErrorPageRegistrar.class, false, false).values());
			Collections.sort(this.registrars, AnnotationAwareOrderComparator.INSTANCE);
			this.registrars = Collections.unmodifiableList(this.registrars);
		
		return this.registrars;
	



至此自动配置就完成了

二、容器是如何启动的

我们知道内嵌Servlet容器的情况下,我们启动SpringBoot的方式一般是SpringApplication.run()方法,它使用的容器默认是AnnotationConfigEmbeddedWebApplicationContext这种类型,它继承自EmbeddedWebApplicationContext,这个类中的两个钩子方法完成容器的启动。

  • createEmbeddedServletContainer
	@Override
	protected void onRefresh() 
		super.onRefresh();
		try 
			createEmbeddedServletContainer();
		
		catch (Throwable ex) 
			throw new ApplicationContextException("Unable to start embedded container",
					ex);
		
	

	@Override
	protected void finishRefresh() 
		super.finishRefresh();
		EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
		if (localContainer != null) 
			publishEvent(
					new EmbeddedServletContainerInitializedEvent(this, localContainer));
		
	
	private void createEmbeddedServletContainer() 
		EmbeddedServletContainer localContainer = this.embeddedServletContainer;
		ServletContext localServletContext = getServletContext();
		if (localContainer == null && localServletContext == null) 
			EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
			this.embeddedServletContainer = containerFactory
					.getEmbeddedServletContainer(getSelfInitializer());
		
		else if (localServletContext != null) 
			try 
				getSelfInitializer().onStartup(localServletContext);
			
			catch (ServletException ex) 
				throw new ApplicationContextException("Cannot initialize servlet context",
						ex);
			
		
		initPropertySources();
	
	protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() 
		// Use bean names so that we don't consider the hierarchy
		String[] beanNames = getBeanFactory()
				.getBeanNamesForType(EmbeddedServletContainerFactory.class);
		if (beanNames.length == 0) 
			throw new ApplicationContextException(
					"Unable to start EmbeddedWebApplicationContext due to missing "
							+ "EmbeddedServletContainerFactory bean.");
		
		if (beanNames.length > 1) 
			throw new ApplicationContextException(
					"Unable to start EmbeddedWebApplicationContext due to multiple "
							+ "EmbeddedServletContainerFactory beans : "
							+ StringUtils.arrayToCommaDelimitedString(beanNames));
		
		return getBeanFactory().getBean(beanNames[0],
				EmbeddedServletContainerFactory.class);
	

	private ServletContextInitializer getSelfInitializer() 
		return new ServletContextInitializer() 
			@Override
			public void onStartup(ServletContext servletContext) throws ServletException 
				selfInitialize(servletContext);
			
		;
	

	private void selfInitialize(ServletContext servletContext) throws ServletException 
		prepareEmbeddedWebApplicationContext(servletContext);
		Configurabl

以上是关于嵌入式Servlet容器自动配置启动自定义配置原理的主要内容,如果未能解决你的问题,请参考以下文章

springboot 嵌入式Servlet容器自动配置原理和容器启动原理

SpringBoot嵌入式Servlet容器自动配置原理

SpringBoot源码学习系列之嵌入式Servlet容器

SpringBoot源码学习系列之嵌入式Servlet容器

spring boot 2 - webrestfulCrud实验错误处理机制配置嵌入式Servlet容器

Spring Boot使用嵌入式容器,自定义Filter如何配置?