在Java开发从工作到原理--BasicErrorController统一异常处理中我们了解到SpringBoot基于Tomcat项目的ErrorPage功能,给我们默认配置了一个ErrorPage用于进行统一的异常处理,其中ErrorPage从Spring上下文到Tomcat上下文的处理过程是由TomcatServletWebServerFactory完成的。
那么我们先来看一下TomcatServletWebServerFactory的来源。
在IDEA中找到ServletWebServerFactoryAutoConfiguration类,是在spring-boot-autoconfigure包中。
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.servlet;
import javax.servlet.DispatcherType;
import javax.servlet.ServletRequest;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.ForwardedHeaderFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for servlet web servers.
*
* @author Phillip Webb
* @author Dave Syer
* @author Ivan Sopov
* @author Brian Clozel
* @author Stephane Nicoll
* @since 2.0.0
*/
@Configuration(proxyBeanMethods = false)
@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);
}
@Bean
@ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class)
@ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")
public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
/**
* Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
* {@link ImportBeanDefinitionRegistrar} for early registration.
*/
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, "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);
}
}
}
}
这个类通过Import注解引入了ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class
这四个类,继续进入ServletWebServerFactoryConfiguration查看对应的代码
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.servlet;
import java.util.stream.Collectors;
import javax.servlet.Servlet;
import io.undertow.Undertow;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.UpgradeProtocol;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.xnio.SslClientAuthMode;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.undertow.UndertowBuilderCustomizer;
import org.springframework.boot.web.embedded.undertow.UndertowDeploymentInfoCustomizer;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration classes for servlet web servers
* <p>
* Those should be {@code @Import} in a regular auto-configuration class to guarantee
* their order of execution.
*
* @author Phillip Webb
* @author Dave Syer
* @author Ivan Sopov
* @author Brian Clozel
* @author Stephane Nicoll
* @author Raheela Asalm
* @author Sergey Serdyuk
*/
@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyServletWebServerFactory JettyServletWebServerFactory(
ObjectProvider<JettyServerCustomizer> serverCustomizers) {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowServletWebServerFactory undertowServletWebServerFactory(
ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
factory.getDeploymentInfoCustomizers()
.addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
}
可以看到是条件化装载对应的实例,起决定性作用的是三个ConditionalOnClass注解,EmbeddedTomcat的这个条件都满足要求,而EmbeddedUndertow和EmbeddedJetty的条件不符,在IDEA中可以清楚的看到有对应的类缺失,从这里可以看到TomcatServletWebServerFactory被加载到Spring的beanFactory中了。所以当程序在刷新ApplicationContext时,从beanFactory中按照ServletWebServerFactory类型获取到的是TomcatServletWebServerFactory实例。
确定了来源那么接下来看看TomcatServletWebServerFactory能干啥。
从名字上来说以Factory结尾的类,结合我们知道的工厂模式的功能,可以猜测这个类是用来创建web server的,tomcat 对应jetty和undertow,servlet对应reactive。
不难发现有一个getWebServer方法:
Tomcat内部层级关系
主要功能:
1、指定tomcat的临时文件存储地址,
2、创建connector对象并指定协议类型,并设置throwOnFailure为true
3、关联connector到tomcat对象中service对象
4、配置connector监听端口,协议信息,传输数据编码信息,设置bindOnInit为false,配置ssl,数据压缩功能
5、关联connector到tomcat对象;
6、设置host对象autoDeploy为false;
7、配置engine对象;
8、添加额外配置的connector到tomcat对象;
9、项目相关TomcatEmbeddedContext配置,并将TomcatEmbeddedContext关联到host对象。
10、启动tomcat,并将tomcat包装为TomcatWebServer返回
而上述第9项TomcatEmbeddedContext配置可更细分为:
1、设置contextpath
2、配置ServletContextInitializer到TomcatStarter(实现ServletContainerInitializer接口),用于Servlet,Filter,Listener,Session的配置。
3、LifecycleListener配置
4、ErrorPage配置
5、MimeMapping配置
6、Session超时时间,Cookie httpOnly配置,session持久化管理器配置
上述第10项在创建TomcatWebServer对象时调用其initialize方法
红框中打印的日志与项目启动日志信息吻合
表明Tomcat正在启动,后续StandardService 以及StandardEngine信息表明Tomcat组件正在启动。
后面的 Tomcat started日志为完成ApplicationContext刷新之后调用TomcatWebServer.start方法进行相关检查,及启动状态更新时打印的。
相关配置都使用ServerProperties中的属性进行配置,基本配置项有:
server.port 监听端口
server.address 服务器有多个ip地址时,绑定监听的IP地址
server.error.path 默认error page访问地址
server.error.includeException 配置http code和异常类型
server.connectionTimeout 连接超时时间
server.ssl ssl相关配置
server.compression 数据压缩相关配置
server.http2.enabled 是否开启http2
server.servlet.contextPath 项目访问路径
server.servlet.contextParameters 对应web.xml中的<context-param></context-param>信息
server.servlet.session.timeout 会话超时时间
server.servlet.session.cookie cookie的相关配置
server.tomcat.basedir tomcat基础路径
server.tomcat.maxThreads 最大工作线程数
server.tomcat.minSpareThreads 最小工作线程数
server.tomcat.maxConnections最大连接数
server.tomcat.acceptCount 等待连接队列大小
server.tomcat.connectionTimeout 连接等待时间
server.tomcat.resource.allowCaching 资源是否允许缓存 告诉浏览器进行缓存
server.tomcat.resource.cacheTtl 资源缓存时间,告诉浏览器缓存多久有效
这些在工作中可能会用到,还有可能会用到的就是基于ServletContextInitializer对象的Servlet,Filter,Listener,Session的配置。
ServletContextInitializer接口的实现类有如下:
剔除抽象类平常会用到的有FilterRegistrationBean,ServletRegistrationBean,ServletListenerRegistrationBean 具体使用方法很简单就不再多赘述了,SessionConfiguringInitializer为内部类,正常工作无法使用