栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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

一、EmbeddedServletContainerAutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
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();
		}

	}
	@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的时候,该自动配置类生效,在内部定义了静态内部类EmbeddedTomcat、EmbeddedUndertow、EmbeddedJetty,即SpringBoot默认支持三种嵌入式Servlet容器,分别在满足的时候自动配置。他们三者都是在容器中没有EmbeddedServletContainerFactory的时候才配置,说明最多只会装配一种容器。以Tomcat为例,它往容器中添加了一个Bean(TomcatEmbeddedServletContainerFactory),该类实现了EmbeddedServletContainerFactory,它用于创建Servlet容器EmbeddedServletContainer。EmbeddedServletContainerFactory的作用后面再表。
    如果我们想换一种容器,我们简单地排除tomcat依赖,引入其容器依赖即可。例如Jetty。
 
	org.springframework.boot 
	spring‐boot‐starter‐web 
	 
		 
			spring‐boot‐starter‐tomcat 
			org.springframework.boot 
		 
	 
 
 
 
	spring‐boot‐starter‐jetty 
	org.springframework.boot 

public interface EmbeddedServletContainerFactory {

	
	EmbeddedServletContainer getEmbeddedServletContainer(
			ServletContextInitializer... initializers);

}
  • EmbeddedServletContainerAutoConfiguration还通过@Import导入了BeanPostProcessorsRegistrar。它往容器中导入了两个Bean:EmbeddedServletContainerCustomizerBeanPostProcessor和ErrorPageRegistrarBeanPostProcessor。
	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 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 getCustomizers() {
		if (this.customizers == null) {
			// Look up does not include the parent context
			this.customizers = new ArrayList(
					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 {

	
	void setContextPath(String contextPath);

	
	void setDisplayName(String displayName);

	
	void setPort(int port);

	
	void setSessionTimeout(int sessionTimeout);

	
	void setSessionTimeout(int sessionTimeout, TimeUnit timeUnit);

	
	void setPersistSession(boolean persistSession);

	
	void setSessionStoreDir(File sessionStoreDir);

	
	void setAddress(InetAddress address);

	
	void setRegisterDefaultServlet(boolean registerDefaultServlet);

	
	void setErrorPages(Set errorPages);

	
	void setMimeMappings(MimeMappings mimeMappings);

	
	void setDocumentRoot(File documentRoot);

	
	void setInitializers(List initializers);

	
	void addInitializers(ServletContextInitializer... initializers);

	
	void setSsl(Ssl ssl);

	
	void setSslStoreProvider(SslStoreProvider sslStoreProvider);

	
	void setJspServlet(JspServlet jspServlet);

	
	void setCompression(Compression compression);

	
	void setServerHeader(String serverHeader);

	
	void setLocaleCharsetMappings(Map localeCharsetMappings);

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

	private ListableBeanFactory beanFactory;

	private List 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 getRegistrars() {
		if (this.registrars == null) {
			// Look up does not include the parent context
			this.registrars = new ArrayList(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);
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
				beanFactory);
		WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
				getServletContext());
		existingScopes.restore();
		WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
				getServletContext());
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}

getEmbeddedServletContainerFactory方法从容器中获取EmbeddedServletContainerFactory,也即是自动配置的,确保只有一个。containerFactory.getEmbeddedServletContainer方法完成Servlet容器的创建。

	@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);
	}
	public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		initialize();
	}

	private void initialize() throws EmbeddedServletContainerException {
		TomcatEmbeddedServletContainer.logger
				.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();
				try {
					// Remove service connectors to that protocol binding doesn't happen
					// yet
					removeServiceConnectors();

					// Start the server to trigger initialization listeners
					this.tomcat.start();

					// We can re-throw failure exception directly in the main thread
					rethrowDeferredStartupExceptions();

					Context context = findContext();
					try {
						ContextBindings.bindClassLoader(context, getNamingToken(context),
								getClass().getClassLoader());
					}
					catch (NamingException ex) {
						// Naming is not enabled. Continue
					}

					// Unlike Jetty, all Tomcat threads are daemon threads. We create a
					// blocking non-daemon to stop immediate shutdown
					startDaemonAwaitThread();
				}
				catch (Exception ex) {
					containerCounter.decrementAndGet();
					throw ex;
				}
			}
			catch (Exception ex) {
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat", ex);
			}
		}
	}

this.tomcat.start();这句话就启动了Servlet容器。

  • startEmbeddedServletContainer
	private EmbeddedServletContainer startEmbeddedServletContainer() {
		EmbeddedServletContainer localContainer = this.embeddedServletContainer;
		if (localContainer != null) {
			localContainer.start();
		}
		return localContainer;
	}

容器启动后其start方法做一些额外的事情

	@Override
	public void start() throws EmbeddedServletContainerException {
		synchronized (this.monitor) {
			if (this.started) {
				return;
			}
			try {
				addPreviouslyRemovedConnectors();
				Connector connector = this.tomcat.getConnector();
				if (connector != null && this.autoStart) {
					startConnector(connector);
				}
				checkThatConnectorsHaveStarted();
				this.started = true;
				TomcatEmbeddedServletContainer.logger
						.info("Tomcat started on port(s): " + getPortsDescription(true));
			}
			catch (ConnectorStartFailedException ex) {
				stopSilently();
				throw ex;
			}
			catch (Exception ex) {
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat servlet container", ex);
			}
			finally {
				Context context = findContext();
				ContextBindings.unbindClassLoader(context, getNamingToken(context),
						getClass().getClassLoader());
			}
		}
	}
三、ServerProperties配置是如何起作用的

我们知道我们可以配置ServerProperties中的配置项来改变servlet容器的配置,那么它是如何起作用的呢?
首先ServerProperties标注了@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true),所以它可以完成属性的自动映射。
其次它还实现了EmbeddedServletContainerCustomizer,就可以对容器进行配置。
所以,如果我们也往容器中放入EmbeddedServletContainerCustomizer而配置项相同,则order顺序在前的配置项会失效。
当然ServerProperties是由ServerPropertiesAutoConfiguration自动配置的。

@Configuration
@EnableConfigurationProperties
@ConditionalOnWebApplication
public class ServerPropertiesAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
	public ServerProperties serverProperties() {
		return new ServerProperties();
	}
四、总结

EmbeddedServletContainerAutoConfiguration完成EmbeddedServletContainerFactory的自动配置,默认支持三种容器Tomcat、Jetty、Undertown,在满足相应条件的时候自动装配一种容器。我们可以通过ServerProperties定义的配置项来修改容器的配置,也可以往容器中添加EmbeddedServletContainerCustomizer来配置容器。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/915470.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号