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。
<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 {
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<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) {
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 {
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<? extends ErrorPage> errorPages);
void setMimeMappings(MimeMappings mimeMappings);
void setDocumentRoot(File documentRoot);
void setInitializers(List<? extends ServletContextInitializer> 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<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) {
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() {
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 {
removeServiceConnectors();
this.tomcat.start();
rethrowDeferredStartupExceptions();
Context context = findContext();
try {
ContextBindings.bindClassLoader(context, getNamingToken(context),
getClass().getClassLoader());
}
catch (NamingException ex) {
}
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 来配置容器。
|