1. 定制容器配置
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-boot-core/tutorial-spring-boot-servlet
工程
1.1 修改配置文件
修改和 server 有关的配置,见 org.springframework.boot.autoconfigure.web.ServerProperties
# 属性都在 ServerProperties 文件中
server.port=8080
server.servlet.context-path=/servlet
1.2 WebServerFactoryCustomizer
定制化 WebServerFactoryCustomizer
容器
@Configuration
public class WebServerFactoryCustomizerConfig {
@Bean
public WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory> webServerFactoryWebServerFactoryCustomizer() {
return factory -> {
// 定制嵌入式的 Servlet 容器相关的规则
factory.setPort(9999);
};
}
}
1.3 Web 容器自动装配原理
1.3.1 Tomcat 自动装配流程图
1.3.2 ServletWebServerFactoryAutoConfiguration 装配原理
它主要是自动装配一个 Web 容器
1. ServletWebServerFactoryAutoConfiguration
给容器中导入嵌入式的容器类,如 EmbeddedTomcat
组件
除此之外,还导入了一个 BeanPostProcessorsRegistrar
组件,这个是用来给容器中添加 WebServerFactoryCustomizerBeanPostProcessor
组件
@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 {
2. EmbeddedTomcat
如果当前环境中有 Servlet、Tomcat、UpgradeProtocol 类,且没有用户自定义的 ServletWebServerFactory
组件,会给容器中注入一个 TomcatServletWebServerFactory
组件
@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();
}
}
3. TomcatServletWebServerFactory
Spring IOC 容器在调用 refresh()
方法时,会调用 onRefresh()
方法,然后调用 createWebServer()
,它会从容器中获取 ServletWebServerFactory
组件,然后调用 getWebServer()
方法
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 创建 tomcat
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);
// 在这里会启动 tomcat,最终会调用 TomcatWebServer 的 initialize 方法启动 tomcat
return getTomcatWebServer(tomcat);
}
4. TomcatWebServer
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
...
// Start the server to trigger initialization listeners
// 启动 tomcat
this.tomcat.start();
...
}
catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
5. BeanPostProcessorsRegistrar
给容器中导入了 WebServerFactoryCustomizerBeanPostProcessor
组件,用来处理自定义属性的
public static class BeanPostProcessorsRegistrar
implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
...
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
// 主要是给容器中导入 WebServerFactoryCustomizerBeanPostProcessor
registerSyntheticBeanIfMissing(registry,
"webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class);
}
...
}
6. WebServerFactoryCustomizerBeanPostProcessor
它是一个 BeanPostProcessor 后置处理器,在这里会调用 WebServerFactoryCustomizer
组件的 customize()
方法
public class WebServerFactoryCustomizerBeanPostProcessor
implements BeanPostProcessor, BeanFactoryAware {
...
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// 判断 bean 类型,属于 WebServerFactory,进行特殊处理
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}
...
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe
.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
...
}
1.3.3 EmbeddedWebServerFactoryCustomizerAutoConfiguration 装配原理
它主要是自动装配一个 Web 容器的属性自定义
1. EmbeddedWebServerFactoryCustomizerAutoConfiguration
ConditionalOnWebApplication
注解表示在 web 环境下生效
@EnableConfigurationProperties(ServerProperties.class)
给容器中导入 ServerProperties
组件,这也就是我们通过配置文件修改 tomcat 属性的原因
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
// 如果当前环境中有 `Tomcat`、`UpgradeProtocol` 类,那么会给容器中导入 `TomcatWebServerFactoryCustomizer`,类似的还有 jetty、undertow、netty 等,所以切换 web 容器时,只需要切换 jar 包即可
@Configuration
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(
Environment environment, ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
...
}
2. TomcatWebServerFactoryCustomizer
根据配置文件的属性修改 tomcat 的值
public class TomcatWebServerFactoryCustomizer implements
WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {
...
// 根据配置文件的属性修改 tomcat 的值
@Override
public void customize(ConfigurableTomcatWebServerFactory factory) {
ServerProperties properties = this.serverProperties;
ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
PropertyMapper propertyMapper = PropertyMapper.get();
propertyMapper.from(tomcatProperties::getBasedir).whenNonNull()
.to(factory::setBaseDirectory);
propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull()
.as(Duration::getSeconds).as(Long::intValue)
.to(factory::setBackgroundProcessorDelay);
customizeRemoteIpValve(factory);
propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive)
.to((maxThreads) -> customizeMaxThreads(factory,
tomcatProperties.getMaxThreads()));
propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive)
.to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads));
propertyMapper.from(this::determineMaxHttpHeaderSize).whenNonNull()
.asInt(DataSize::toBytes).when(this::isPositive)
.to((maxHttpHeaderSize) -> customizeMaxHttpHeaderSize(factory,
maxHttpHeaderSize));
propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull()
.asInt(DataSize::toBytes)
.to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
propertyMapper.from(tomcatProperties::getMaxHttpPostSize).asInt(DataSize::toBytes)
.when((maxHttpPostSize) -> maxHttpPostSize != 0)
.to((maxHttpPostSize) -> customizeMaxHttpPostSize(factory,
maxHttpPostSize));
propertyMapper.from(tomcatProperties::getAccesslog)
.when(ServerProperties.Tomcat.Accesslog::isEnabled)
.to((enabled) -> customizeAccessLog(factory));
propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull()
.to(factory::setUriEncoding);
propertyMapper.from(properties::getConnectionTimeout).whenNonNull()
.to((connectionTimeout) -> customizeConnectionTimeout(factory,
connectionTimeout));
propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive)
.to((maxConnections) -> customizeMaxConnections(factory, maxConnections));
propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive)
.to((acceptCount) -> customizeAcceptCount(factory, acceptCount));
customizeStaticResources(factory);
customizeErrorReportValve(properties.getError(), factory);
}
...
}
2. 注册 Servlet 三大组件
2.1 注册 servlet
1. MyServlet
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("Hello SpringBoot Servlet");
}
}
2. ServletConfig
@Configuration
public class ServletConfig {
@Bean
public ServletRegistrationBean<MyServlet> myServlet() {
return new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
}
}
2.2 注册 filter
1. MyFilter
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("MyFilter process...");
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("MyFilter destroy");
}
}
2. FilterConfig
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new MyFilter());
return filterRegistrationBean;
}
}
2.3 注册 listener
1. MyListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("contextInitialized...web 应用启动");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed...web 应用销毁");
}
}
2. ListenerConfig
@Configuration
public class ListenerConfig {
@Bean
public ServletListenerRegistrationBean<MyListener> myListener() {
ServletListenerRegistrationBean<MyListener> listenerRegistrationBean = new ServletListenerRegistrationBean<>();
listenerRegistrationBean.setListener(new MyListener());
return listenerRegistrationBean;
}
}
3. 使用外置的 Servlet 容器
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-boot-core/tutorial-spring-boot-war
工程
3.1 嵌入式 Servlet 容器
嵌入式 Servlet 容器可以把应用打成可执行的 jar,但是默认不支持 JSP,优化定制也比较复杂
也可以使用外置的 Servlet 容器,将应用打成 war 包部署
3.2 使用 war 包部署
- 修改打包方式
<packaging>war</packaging>
- 修改
spring-boot-starter-tomcat
依赖为provided
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
3. 编写 ServletInitializer 类
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入 SpringBoot 应用的主程序
return application.sources(WarApplication.class);
}
}
3.3 使用外置容器启动原理
3.3.1 servlet3.0 新特性
服务器启动(web 应用启动)会创建当前 web 应用里面每一个 jar 包里面 ServletContainerInitializer 实例,然后调用它们的 onStartup()
方法
ServletContainerInitializer 的实现放在 jar 包的 META-INF/services
文件夹下,有一个名为 javax.servlet.ServletContainerInitializer
的文件,内容就是 ServletContainerInitializer 的实现类的全类名
可以使用 @HandlesTypes
注解在应用启动的时候加载指定的类
3.3.2 spring-web-xxx.RELEASE.jar
在 spring-web-xxx.RELEASE.jar
里,META-INF/services/javax.servlet.ServletContainerInitializer
所对应的内容是 org.springframework.web.SpringServletContainerInitializer
3.3.3 SpringServletContainerInitializer
在这个类中,@HandlesTypes
对应的类型是 WebApplicationInitializer
我们写的 ServletInitializer 类是继承 SpringBootServletInitializer,它实现了 WebApplicationInitializer 接口
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
// 实例化 WebApplicationInitializer,然后放到 initializers 中
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
// 循环执行 onStartup 方法启动
initializer.onStartup(servletContext);
}
}
}
3.3.4 SpringBootServletInitializer
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
// 调用下面的方法创建根容器
WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext);
...
}
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
// 创建 SpringApplicationBuilder
SpringApplicationBuilder builder = createSpringApplicationBuilder();
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
// 调用 configure 方法,子类重写了这个方法,将 SpringBoot 的主程序类传入了进来
builder = configure(builder);
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(getClass()));
}
Assert.state(!application.getAllSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.addPrimarySources(
Collections.singleton(ErrorPageFilterConfiguration.class));
}
// 启动 Spring 应用,和之前用 main 方法启动的流程是一样的
return run(application);
}