Spring Boot源码解析——Spring Boot的Web原理
文章目录
一、Servlet 3.0 规范
1.1 不用web.xml的两种方法
-
ServletContainerInitializer 是 Servlet 3.0 新增的一个接口,服务器启动(web应用启动)时会创建当前web应用里每一个jar包里面的ServletContainerInitializer实例。ServletContainer 其实就是 Servlet、Filter、Listener 的总称。
-
ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名。
-
还可以使用 @HandlesTypes,在应用启动的时候加载感兴趣的类。
-
容器将 WEB-INF/lib 目录下 JAR 包中的类都交给ServletContainerInitializer 的实现类的 onStartup() 方法处理,通常需要在该实现类上使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 onStartup() 处理的类。
-
Servlet3.0 提供了 @WebServlet,@WebFilter 等注解,这样便有了抛弃 web.xml 的一个途径:凭借注解声明Servlet 和 Filter 来做到这一点。
1.2 Spring对Servlet 3.0的支持——SpringServletContainerInitializer
SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer .class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>,为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) {
// 非接口非抽象类才能进行创建
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
// 反射创建 WebApplicationInitializer 类型的类
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
...
AnnotationAwareOrderComparator.sort(initializers);
// 利用 WebApplicationInitializer 的 onStartup() 方法对 servlet 和 filter 进行注册
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
WebApplicationInitializer 的三个实现类:
二、Spring Boot加载Servlet等组件
2.1 不用web.xml进行三大组件(Servlet、Filter、Listener)的注册
2.1.1 Servlet 3.0注解+@ServletComponentScan
使用@WebServlet、@WebFilter、@WebListener等注解进行注册,同时在主启动类上加@ServletComponentScan去扫描到这些注解。
2.1.2 RegistrationBean
RegistrationBean 是 Spring Boot 中广泛应用的一个注册类,负责把 Servlet、Filter、Listener 给容器化,能够被Spring 托管,并且完成自身对 web 容器的注册。
- ServletRegistrationBean
- FilterRegistrationBean
- ServletListenerRegistrationBean
@Configuration
public class MyServerConfig {
//注册三大组件
@Bean
public ServletRegistrationBean myServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),
"/myServlet");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return registrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> registrationBean = new
ServletListenerRegistrationBean<>(new MyListener());
return registrationBean;
}
}
2.2 Servlet加载流程原理
Spring Boot一般都使用内嵌 Tomcat 容器,但部署时存在两种选择:一种是打成 jar 包,使用 java -jar 的方式运行;另一种是打成 war 包,交给外置容器去运行。
Spring Boot 没有完全遵守 Servlet3.0 规范,完全没有使用 SpringServletContainerInitializer。为了在打成 jar 包的情况下,能够搜索到内嵌的Servlet容器,可以使用ServletContextInitializer,RegistrationBean 就实现了该接口。
Spring Boot在加载Servlet容器时,进入了TomcatStarter类,它负责调用一系列 ServletContextInitializer 的 onStartup 方法:
class TomcatStarter implements ServletContainerInitializer {
...
private final ServletContextInitializer[] initializers;
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
// 调用一系列 ServletContextInitializer 的 onStartup 方法
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
...
}
- 其中加载了EmbeddedWebApplicationContext的匿名内部类,可获取到所有的Servlet、Filter、Listener:
2.2.1 EmbeddedWebApplicationContext全流程 + 嵌入式Servlet容器(Tomcat)启动原理
- EmbeddedWebApplicationContext的核心在于对onRefresh()的重写:
@Override
protected void onRefresh() {
super.onRefresh();
try {
// 主要是执行该方法
createEmbeddedServletContainer();
}
...
}
- createEmbeddedServletContainer()——创建一个内嵌的 Servlet 容器
Tomcat启动关键:
- 在Spring Boot应用运行run方法的过程中,根据当前环境创建IOC容器对象。
- 如果是Web应用,则创建AnnotationConfigEmbeddedWebApplicationContext,它继承了EmbeddedWebApplicationContext;否则创建AnnotationConfigApplicationContext。
- 进入上面的onRefresh()逻辑,Web的IoC容器获取嵌入式的Servlet容器工厂:getEmbeddedServletContainerFactory(),会返回TomcatEmbeddedServletContainerFactory(在这里,对应第三章节的TomcatServletWebServerFactory)。
- 工厂调用getEmbeddedServletContainer(对应于第三章的getWebServer),直接创建Tomcat并启动容器。
- 在创建启动Tomcat前,先getSelfInitializer()进行初始化流程,实现三大组件的注册加载。
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = getServletContext();
// 在都为空的情况下,先进行初始化流程
if (localContainer == null && localServletContext == null) {
// 获取 TomcatEmbeddedServletContainerFactory(或对应第三章节的 TomcatServletWebServerFactory),它是服务器启动的上层抽象
// 无论是 Tomcat 还是 Jetty,需要通过该类实现对 Spring 服务器的注册
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
// 核心:启动 Tomcat
// getEmbeddedServletContainer() 方法
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
}
else if (localServletContext != null) {
try {
getSelfInitializer().onStartup(localServletContext);
}
...
}
initPropertySources();
}
- getSelfInitializer()——进行真正的初始化流程——在这里实现了三大组件的注册加载
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
// 创建一个 ServletContextInitializer 匿名内部类
return new ServletContextInitializer() {
@Override
// 在 TomcatStarter 中调用 ServletContextInitializer 匿名内部类的 onStartup 方法就是这个
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());
// 隐式触发了 selfInitialize 方法,通过 getServletContextInitializerBeans() 获取 RegisterBean
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
// 最终在这里实现了三大组件的注册加载
beans.onStartup(servletContext);
}
}
- 其中调用了getServletContextInitializerBeans(),用于获取RegisterBean等。
- ===》ServletContextInitializerBeans(getBeanFactory()) 用于加载 Servlet 和 Filter ===》addServletContextInitializerBeans()去容器中寻找注册过的 ServletContextInitializer,这时就可以把之前配置的那些 RegisterBean 全部加载出来。
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Entry<String, ServletContextInitializer> initializerBean :
// 获取到了配置的 RegisterBean
getOrderedBeansOfType(beanFactory, ServletContextInitializer.class)) {
addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
}
}
总结:
- EmbeddedWebApplicationContext 的 onRefresh() 方法触发配置匿名内部类 ServletContextInitializer。
- 其 onStartup 方法会去容器中搜索所有的 RegisterBean 并按顺序加载到 ServletContext 中。
- 匿名内部类 ServletContextInitializer 最终会传递给 TomcatStarter,由 TomcatStarter 的 onStartup 方法去触发 ServletContextInitializer 的 onStartup 方法,最终完成装配。
三、嵌入式Servlet容器自动配置原理
Spring Boot 默认使用Tomcat作为嵌入式的Servlet容器,Spring Boot 默认是以jar包的方式启动嵌入式的Servlet容器来启动Spring Boot的web应用,没有web.xml文件。Spring Boot自动配置Spring MVC的时候,自动地注册Spring MVC的前端控制器DispatcherServlet。
- 编写一个WebServerFactoryCustomizer定制嵌入式的Servlet容器相关的规则。
@Configuration
public class MyServerConfig {
// 配置嵌入式的 Servlet 容器
@Bean
public WebServerFactoryCustomizer webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
// 定制嵌入式的 Servlet 容器相关的规则
@Override
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8083);
}
};
}
}
3.1 ServletWebServerFactoryAutoConfiguration
步骤:
- Spring Boot根据导入的依赖情况,给容器中添加相应的XXX-ServletWebServerFactory;
- 容器中某个组件要创建对象就会惊动后置处理器WebServerFactoryCustomizerBeanPostProcessor,只要是嵌入式的Servlet容器工厂,后置处理器就工作;
- 后置处理器会从容器中获取所有的WebServerFactoryCustomizer,调用定制器(ServerProperties)的定制方法给工厂添加配置。
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({
// 导入了 WebServerFactoryCustomizerBeanPostProcessor
ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
// 三种嵌入式 Servlet 容器
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
...
}
- EmbeddedTomcat
@Configuration(proxyBeanMethods = false)
// 引入了 Tomcat 依赖才会生效
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
// 没有自定义的 ServletWebServerFactory 才生效
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
...
}
- ServletWebServerFactory——嵌入式的Web服务器工厂接口
@FunctionalInterface
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
3.1.1 TomcatServletWebServerFactory
- 实现的getWebServer() 方法
该方法创建一个Tomcat,同时配置Tomcat的基本环境,(Tomcat的配置都是从本类获取的,tomcat.setXXX),最后将配置好的Tomcat传入getTomcatWebServer,返回一个WebServer,并且启动Tomcat服务器。
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// 创建一个 Tomcat
Tomcat tomcat = new Tomcat();
// 配置 Tomcat 基本环境
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
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,并启动 Tomcat 服务器
return getTomcatWebServer(tomcat);
}
- getTomcatWebServer ===》 TomcatWebServer:
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
// 端口号大于 0 就会启动
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
// TomcatWebServer 构造
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
initialize();
}
- initialize()
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
removeServiceConnectors();
}
});
// 启动 Tomcat
this.tomcat.start();
...
}
1.4 外置的Servlet容器启动Spring Boot应用
-
jar包:执行Spring Boot主类main()方法,先启动IoC容器,创建嵌入式Servlet容器。
-
war包:先启动服务器,服务器启动Spring Boot应用(SpringBootServletInitializer),再启动IoC容器。
-
流程:
-
启动Tomcat服务器;
-
META-INF\services\javax.servlet.ServletContainerInitializer 配置文件中进行配置:org.springframework.web.SpringServletContainerInitializer;
-
SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer
.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>,为WebApplicationInitializer类型的类创建实例。 -
每一个WebApplicationInitializer都调用自己的onStartup();