该系列文档是本人在学习 Spring MVC 的源码过程当中总结下来的,可能对读者不太友好,请结合个人源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读html
Spring 版本:5.2.4.RELEASEjava
在开始 Spring MVC
的分析以前,先来聊一聊 Java 初学者接触的最多的 Java Web 基础。还记得个人第一个 Web 工程是由 Servlet、Velocity 和 Filter 来完成的,那时几乎全部人都是根据 Servlet、JSP 和 Filter 来编写本身的第一个 Hello World 工程。那时,还离不开 web.xml 配置文件,须要对 Servlet 和 Filter 进行配置,相对来讲比较繁琐。随着 Spring
体系的快速发展,配置逐渐演变成了 Java Configuration 和 XML 配置两种方式的共存。现现在,Spring Boot
和 Spring Cloud
在许多中大型企业中被普及,Java Configuration 成为了主流,XML 配置的方式也逐渐“消失”在咱们的视野里面。不知道如今的小伙伴是否还记得那个 web.xml 文件,这中间都发生过什么变化,其中的 Servlet 和 Filter 配置项被什么取代了?git
Servlet:Java Servlet 为 Web 开发人员提供了一种简单,一致的机制,以扩展 Web 服务器的功能并访问现有的业务系统。实现了 Servlet 接口的类在 Servlet 容器中可用于处理请求并发送响应。github
Tomcat:Tomcat 是 Web 应用服务器,是一个 Servlet 容器,实现了对 Servlet 和 JSP 的支持。web
若是应用程序是以 war 包的方式放入 Tomcat 的 webapps 文件夹下面,那么在 Tomcat 启动时会加载 war 包,生成对应的一个文件夹,Tomcat 则会去对 webapps 文件夹下面的每个文件夹(咱们的应用程序)生成一个部署任务,去解析对应的 WEB-INF/web.xml 文件,将配置的 Servlet 加载到 Servlet 容器中。当 Tomcat 监听到某端口的 HTTP 请求时,则会将请求解析成 Request 对象,而后交由相应的 Servlet 进行处理,最后将处理结果转换成 HTTP 响应。算法
为何是 webapps 目录和 WEB-INF/web.xml 文件,能够看一下 Tomcat 的 conf/server.xml 和 conf/context.xml 两个配置文件,以下:spring
<!-- server.xml --> <!-- appBase 属性指定应用程序所在目录 --> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <!-- context.xml --> <Context> <!-- Default set of monitored resources. If one of these changes, the web application will be reloaded. --> <WatchedResource>WEB-INF/web.xml</WatchedResource> <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource> <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource> </Context>
为了体现出整个演进过程,先来回顾下当初咱们是怎么写 Servlet 和 Filter 代码来完成本身的第一个 Hello World 工程apache
项目结构数组
. ├── pom.xml ├── src ├── main │ ├── java │ │ └── cn │ │ └── edu │ │ └── shopping │ │ ├── filter │ │ │ └── HelloWorldFilter.java │ │ └── servlet │ │ └── HelloWorldServlet.java │ └── webapp │ └── WEB-INF │ └── web.xml └── test └── java
cn.edu.shopping.servlet.HelloWorldServlet.java
浏览器
public class HelloWorldServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/plain"); PrintWriter writer = response.getWriter(); writer.println("Hello World"); } }
cn.edu.shopping.filter.HelloWorldFilter.java
public class HelloWorldFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("触发 Hello World 过滤器..."); chain.doFilter(request, response); } @Override public void destroy() { } }
在 web.xml 中配置 Servlet 和 Filter
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>HelloWorldServlet</servlet-name> <servlet-class>cn.edu.shopping.servlet.HelloWorldServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorldServlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <filter> <filter-name>HelloWorldFilter</filter-name> <filter-class>cn.edu.shopping.filter.HelloWorldFilter</filter-class> </filter> <filter-mapping> <filter-name>HelloWorldFilter</filter-name> <url-pattern>/hello</url-pattern> </filter-mapping> </web-app>
上述就是我当初第一个 Hello World 工程,配置 Tomcat 后启动,在浏览器里面输入 http://127.0.0.1:8080/hello
可看到 “Hello World”,在控制台会打印“触发 Hello World 过滤器...”
参考 IBM 的 Servlet 3.0 新特性详解 文章
Servlet 3.0 做为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一块儿发布。该版本在前一版本(Servlet 2.5)的基础上提供了若干新特性用于简化 Web 应用的开发和部署。其中有几项特性的引入让开发者感到很是兴奋,同时也得到了 Java 社区的一片赞誉之声:
经过 Servlet3.0 首先提供了 @WebServlet
、@WebFilter
和 @WebListener
等注解,能够替代 web.xml 文件中的 Servlet 和 Filter 等配置项
除了以上的新特性以外,ServletContext 对象的功能在新版本中也获得了加强。如今,该对象支持在运行时动态部署 Servlet、过滤器、监听器,以及为 Servlet 和过滤器增长 URL 映射等。以 Servlet 为例,过滤器与监听器与之相似。ServletContext 为动态配置 Servlet 增长了以下方法:
ServletRegistration.Dynamic addServlet(String servletName,Class<? extends Servlet> servletClass)
ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
ServletRegistration.Dynamic addServlet(String servletName, String className)
其中前三个方法的做用是相同的,只是参数类型不一样而已;经过 createServlet() 方法建立的 Servlet,一般须要作一些自定义的配置,而后使用 addServlet() 方法来将其动态注册为一个能够用于服务的 Servlet。两个 getServletRegistration() 方法主要用于动态为 Servlet 增长映射信息,这等价于在 web.xml( 抑或 web-fragment.xml) 中使用 标签为存在的 Servlet 增长映射信息。
以上 ServletContext 新增的方法要么是在 ServletContextListener
的 contexInitialized 方法中调用,要么是在 ServletContainerInitializer
的 onStartup()
方法中调用。
ServletContainerInitializer 也是 Servlet 3.0 新增的一个接口,容器在启动时使用 JAR 服务 API(JAR Service API) 来发现 ServletContainerInitializer 的实现类,而且容器将 WEB-INF/lib 目录下 JAR 包中的类都交给该类的 onStartup() 方法处理,咱们一般须要在该实现类上使用 @HandlesTypes 注解来指定但愿被处理的类,过滤掉不但愿给 onStartup() 处理的类。
一个典型的 Servlet3.0+ 的 Web 项目结构以下:
. ├── pom.xml ├── src ├── main │ ├── java │ │ └── cn │ │ └── edu │ │ └── shopping │ │ ├── CustomServletContainerInitializer.java │ │ ├── filter │ │ │ └── HelloWorldFilter.java │ │ └── servlet │ │ └── HelloWorldServlet.java │ ├── resources │ │ └── META-INF │ │ └── services │ │ └── javax.servlet.ServletContainerInitializer │ └── webapp │ └── WEB-INF │ └── web.xml └── test └── java
HelloWorldFilter 和 HelloWorldServlet 没有变更,新增了一个 CustomServletContainerInitializer 对象,它实现了 javax.servlet.ServletContainerInitializer
接口,用来在 Web 容器启动时加载须要的 Servlet 和 Filter,代码以下:
public class CustomServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { System.out.println("建立 Hello World Servlet..."); javax.servlet.ServletRegistration.Dynamic servlet = ctx.addServlet( HelloWorldServlet.class.getSimpleName(), HelloWorldServlet.class); servlet.addMapping("/hello"); System.out.println("建立 Hello World Filter..."); javax.servlet.FilterRegistration.Dynamic filter = ctx.addFilter(HelloWorldFilter.class.getSimpleName(), HelloWorldFilter.class); EnumSet<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class); dispatcherTypes.add(DispatcherType.REQUEST); dispatcherTypes.add(DispatcherType.FORWARD); filter.addMappingForUrlPatterns(dispatcherTypes, true, "/hello"); } }
在实现的 onStartup 方法中向 ServletContext 对象(Servlet 上下文)添加以前在 web.xml 中配置的 HelloWorldFilter 和 HelloWorldServlet,这样一来就能够去除 web.xml 文件了。
方法入参中的 Set<Class<?>> c
是和 @HandlesTypes 注解结合使用的,指定须要处理的 Calss 类,能够参考 Spring 中的 SpringServletContainerInitializer
使用方法
这么声明一个 ServletContainerInitializer 的实现类,Web 容器并不会识别它,须要借助 SPI 机制来指定该初始化类,经过在项目 ClassPath 路径下建立 META-INF/services/javax.servlet.ServletContainerInitializer
文件来作到的,内容以下:
cn.edu.shopping.CustomServletContainerInitializer
这样一来,使用 ServletContainerInitializer 和 SPI 机制则能够拜托 web.xml 了。
回到 Spring 全家桶,你可能已经忘何时开始不写 web.xml 了,如今的项目基本看不到它了,Spring 又是如何支持 Servlet3.0 规范的呢?
在 Spring 的 spring-web
子工程的 ClassPath 下面的有一个 META-INF/services/javax.servlet.ServletContainerInitializer
文件,以下:
org.springframework.web.SpringServletContainerInitializer
org.springframework.web.SpringServletContainerInitializer
类,代码以下:
/** * Servlet 3.0 {@link ServletContainerInitializer} designed to support code-based * configuration of the servlet container using Spring's {@link WebApplicationInitializer} * SPI as opposed to (or possibly in combination with) the traditional * {@code web.xml}-based approach. */ @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... // <1> if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { 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) { // <2> initializer.onStartup(servletContext); } } }
注意我在源码中标注两个序号,这对于咱们理解 Spring 装配 Servlet 的流程来讲很是重要
<1>
提示咱们因为 Servlet 厂商实现的差别,onStartup 方法会加载咱们本不想处理的 Class 对象,因此进行了特判。
<2>
Spring 与咱们上述提供的 Demo 不一样,并无在 SpringServletContainerInitializer 中直接对 Servlet 和 Filter 进行注册,而是委托给了一个陌生的类 WebApplicationInitializer ,这个类即是 Spring 用来初始化 Web 环境的委托者类,它的实现类:
你必定不会对 DispatcherServlet 感到陌生,他就是 Spring MVC 中的核心类,AbstractDispatcherServletInitializer
即是无 web.xml
前提下,建立 DispatcherServlet 的关键类,代码以下:
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { // 调用父类启动的逻辑 super.onStartup(servletContext); // 注册 DispacherServlt registerDispatcherServlet(servletContext); } protected void registerDispatcherServlet(ServletContext servletContext) { // 得到 Servlet 名 String servletName = getServletName(); Assert.hasLength(servletName, "getServletName() must not return null or empty"); // <1> 建立 WebApplicationContext 对象 WebApplicationContext servletAppContext = createServletApplicationContext(); Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null"); // <2> 建立 FrameworkServlet 对象 FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null"); dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); if (registration == null) { throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " + "Check if there is another servlet registered under the same name."); } registration.setLoadOnStartup(1); registration.addMapping(getServletMappings()); registration.setAsyncSupported(isAsyncSupported()); // <3> 注册过滤器 Filter[] filters = getServletFilters(); if (!ObjectUtils.isEmpty(filters)) { for (Filter filter : filters) { registerServletFilter(servletContext, filter); } } customizeRegistration(registration); } }
<1>
处,调用 createServletApplicationContext()
方法,建立 WebApplicationContext 对象,代码以下:
// AbstractAnnotationConfigDispatcherServletInitializer.java @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); Class<?>[] configClasses = getServletConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { context.register(configClasses); } return context; }
<2>
处,调用 createDispatcherServlet(WebApplicationContext servletAppContext)
方法,建立 FrameworkServlet 对象,代码以下:
// AbstractDispatcherServletInitializer.java protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) { return new DispatcherServlet(servletAppContext); }
servletAppContext
方法参数,这就是该 DispatcherServlet 的 Servlet WebApplicationContext 容器注意,上述这一切特性从 Spring 3 就已经存在了,而现在 Spring 5 已经伴随 SpringBoot 2.0 一块儿发行了
读到这儿,你已经阅读了全文的 1/2。SpringBoot 对于 Servlet 的处理才是重头戏,由于 SpringBoot 使用范围很广,不多有人用 Spring 而不用 SpringBoot 了
是的,前面所讲述的 Servlet 的规范,不管是 web.xml 中的配置,仍是 Servlet3.0 中的 ServletContainerInitializer 和 SpringBoot 的加载流程都没有太大的关联。按照惯例,先卖个关子,先看看如何在 SpringBoot 中注册 Servlet 和 Filter,再来解释下 SpringBoot 的独特之处
SpringBoot 依旧兼容 Servlet 3.0 一系列以 @Web*
开头的注解:@WebServlet
,@WebFilter
,@WebListener
@WebServlet("/hello") public class HelloWorldServlet extends HttpServlet{} @WebFilter("/hello/*") public class HelloWorldFilter implements Filter {}
在启动类上面添加 @ServletComponentScan
注解去扫描到这些注解
@SpringBootApplication @ServletComponentScan public class SpringBootServletApplication { public static void main(String[] args) { SpringApplication.run(SpringBootServletApplication.class, args); } }
这种方式相对来讲比较简介直观,其中 org.springframework.boot.web.servlet.@ServletComponentScan
注解经过 @Import(ServletComponentScanRegistrar.class)
方式,它会将扫描到的 @WebServlet
、@WebFilter
、@WebListener
的注解对应的类,最终封装成 FilterRegistrationBean、ServletRegistrationBean、ServletListenerRegistrationBean 对象,注册到 Spring 容器中。也就是说,和注册方式二:RegistrationBean统一了
@Configuration public class WebConfig { @Bean public ServletRegistrationBean<HelloWorldServlet> helloWorldServlet() { ServletRegistrationBean<HelloWorldServlet> servlet = new ServletRegistrationBean<>(); servlet.addUrlMappings("/hello"); servlet.setServlet(new HelloWorldServlet()); return servlet; } @Bean public FilterRegistrationBean<HelloWorldFilter> helloWorldFilter() { FilterRegistrationBean<HelloWorldFilter> filter = new FilterRegistrationBean<>(); filter.addUrlPatterns("/hello/*"); filter.setFilter(new HelloWorldFilter()); return filter; } }
ServletRegistrationBean 和 FilterRegistrationBean 都继成 RegistrationBean,它是 SpringBoot 中普遍应用的一个注册类,负责把 Servlet,Filter,Listener 给容器化,使它们被 Spring 托管,而且完成自身对 Web 容器的注册,这种注册方式值得推崇
从图中能够看出 RegistrationBean 的地位,它的几个实现类做用分别是:
暂时只介绍上面两种方式,接下来开始讨论 SpringBoot 中 Servlet 的加载流程,讨论的前提是 SpringBoot 环境下使用内嵌的容器,好比最典型的 Tomcat
当使用内嵌的 Tomcat 时,你在 SpringServletContainerInitializer 上面打断点,会发现根本不会进入该类的内部,由于 SpringBoot 彻底走了另外一套初始化流程,而是进入了 org.springframework.boot.web.embedded.tomcat.TomcatStarter
这个类
仔细扫一眼源码包,并无发现有 SPI 文件对应到 TomcatStarter,也就是说没有经过 SPI 机制加载这个类,为何没有这么作呢?能够翻阅 Spring Github 中的 issue,其中有 Spring 做者确定的答复:https://github.com/spring-projects/spring-boot/issues/321
This was actually an intentional design decision. The search algorithm used by the containers was problematic. It also causes problems when you want to develop an executable WAR as you often want a
javax.servlet.ServletContainerInitializer
for the WAR that is not executed when you runjava -jar
.See the
org.springframework.boot.context.embedded.ServletContextInitializer
for an option that works with Spring Beans.
SpringBoot 这么作是有意而为之,咱们在使用 SpringBoot 时,开发阶段通常都是使用内嵌 Tomcat 容器,但部署时却存在两种选择:一种是打成 jar 包,使用 java -jar
的方式运行;另外一种是打成 war 包,交给外置容器去运行。
前者就会致使容器搜索算法出现问题,由于这是 jar 包的运行策略,不会按照 Servlet 3.0 的策略去加载 ServletContainerInitializer!
最后做者还提供了一个替代选项:ServletContextInitializer,它和 ServletContainerInitializer 长得特别像,别搞混淆了!
org.springframework.boot.web.servlet.ServletContextInitializer
javax.servlet.ServletContainerInitializer
,前文提到的 RegistrationBean 就实现了 ServletContextInitializer 接口TomcatStarter 中 org.springframework.boot.context.embedded.ServletContextInitializer[] initializers
属性,是 SpringBoot 初始化 Servlet,Filter,Listener 的关键,代码以下:
class TomcatStarter implements ServletContainerInitializer { private static final Log logger = LogFactory.getLog(TomcatStarter.class); private final ServletContextInitializer[] initializers; private volatile Exception startUpException; TomcatStarter(ServletContextInitializer[] initializers) { this.initializers = initializers; } @Override public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException { try { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } } catch (Exception ex) { this.startUpException = ex; // Prevent Tomcat from logging and re-throwing when we know we can // deal with it in the main thread, but log for information here. if (logger.isErrorEnabled()) { logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: " + ex.getMessage()); } } } public Exception getStartUpException() { return this.startUpException; } }
在 onStartup(Set<Class<?>> classes, ServletContext servletContext)
方法中,负责调用一系列的 ServletContextInitializer 对象的 onStartup 方法
那么在 debug 的过程当中,构造方法中的 ServletContextInitializer[] initializers
入参到底包含了哪些类呢?会不会有咱们前面介绍的 RegistrationBean 呢?
RegistrationBean 并无出如今 TomcatStarter 的 debug 信息中,initializers
包含了三个类,其中只有第 3
个类看上去比较核心,ServletWebServerApplicationContext 的 子类 AnnotationConfigServletWebServerApplicationContext 对象,为了搞清楚 SpringBoot 如何加载 Filter、Servlet、Listener ,看来还得研究下 ServletWebServerApplicationContext 对象
上面是基于 SpringBoot 2.0.3.RELEASE 版本作的总体分析,若是是其余版本,可能会存在部分差别,不过原理都相同,不会有太大的变化
ApplicationContext 你们应该是比较熟悉的,这是 Spring 一个比较核心的类,通常咱们能够从中获取到那些注册在容器中的托管 Bean,而这篇文章,主要分析的即是它在内嵌容器中的实现类:org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
,重点分析它加载 Filter、Servlet 和 Listener 这部分的代码。
这里是整个代码中迭代层次最深的部分,作好心理准备起航,来看看 ServletWebServerApplicationContext 是怎么获取到全部的 Filter、Servlet 和 Listener 对象的,如下方法大部分出自于 ServletWebServerApplicationContext
onRefresh()
方法,是 ApplicationContext 的生命周期方法,ServletWebServerApplicationContext 的实现很是简单,只干了一件事:
@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); //第二层的入口 } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
看名字 Spring 是想建立一个内嵌的 Web 容器,代码以下:
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); // 第三层的入口 } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
凡是带有 Servlet,Initializer 字样的方法,都是咱们须要留意的。其中 getSelfInitializer()
方法,便涉及到了咱们最为关心的初始化流程,因此接着链接到了第三层
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(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); } }
还记得前面 TomcatStarter 的 debug 信息中,第 3
个 ServletContextInitializer 就是在 ServletWebServerApplicationContext 这里的 getSelfInitializer()
方法中建立的
解释下这里的 getSelfInitializer()
和 selfInitialize(ServletContext servletContext)
方法,为何要这么设计?
这是典型的回调式方式,当匿名 ServletContextInitializer 类被 TomcatStarter 的 onStartup()
方法调用,设计上是触发了 selfInitialize(ServletContext servletContext)
方法的调用
因此这下就清晰了,为何 TomcatStarter 中没有出现 RegistrationBean ,实际上是隐式触发了 ServletWebServerApplicationContext 中的 selfInitialize(ServletContext servletContext)
方法。这样,在 selfInitialize(ServletContext servletContext)
方法中,调用 getServletContextInitializerBeans()
方法,得到 ServletContextInitializer 数组就成了关键
/** * Returns {@link ServletContextInitializer}s that should be used with the embedded web server. * By default this method will first attempt to find {@link ServletContextInitializer}, * {@link Servlet}, {@link Filter} and certain {@link EventListener} beans. * @return the servlet initializer beans */ protected Collection<ServletContextInitializer> getServletContextInitializerBeans() { return new ServletContextInitializerBeans(getBeanFactory()); //第五层的入口 }
从注释中能够知晓这个 ServletContextInitializerBeans 类,就是用来加载 Servlet 和 Filter 的
org.springframework.boot.web.servlet.ServletContextInitializerBeans
public ServletContextInitializerBeans(ListableBeanFactory beanFactory) { this.initializers = new LinkedMultiValueMap<>(); addServletContextInitializerBeans(beanFactory); // 第六层的入口 addAdaptableBeans(beanFactory); List<ServletContextInitializer> sortedInitializers = this.initializers.values() .stream() .flatMap((value) -> value.stream() .sorted(AnnotationAwareOrderComparator.INSTANCE)) .collect(Collectors.toList()); this.sortedList = Collections.unmodifiableList(sortedInitializers); }
// ServletContextInitializerBeans.java private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) { for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType( beanFactory, ServletContextInitializer.class)) { addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory); } }
getOrderedBeansOfType(beanFactory, ServletContextInitializer.class)
方法,即是去 Spring 容器中寻找注册过的 ServletContextInitializer 对象们,这时候就能够把以前那些 RegistrationBean 所有加载出来了,而且 RegistrationBean 还实现了 Ordered 接口,在这儿用于排序
若是你对具体的代码流程不感兴趣,能够跳过上述的 6 层分析,直接看本节的结论,总结以下:
从上图中能够看到,咱们配置的 Filter 和 Servlet 注册类都获取到了,而后调用其 onStartup 方法,进去后你会发现调用 ServletContext 对象的 addServlet 方法注册 Servlet,能够返回 Servlet3.0 新特性小节中回顾一下
研究完了上述 SpringBoot 加载 Servlet 的内部原理,能够发现 ServletContextInitializer 实际上是 Spring 中 ServletContainerInitializer 的代理,虽然 SpringBoot 中 Servlet3.0 不起做用了,但它的代理仍是会被加载的,因而咱们有了第三种方式注册 servlet
@Configuration public class CustomServletContextInitializer implements ServletContextInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { System.out.println("建立 Hello World Servlet..."); javax.servlet.ServletRegistration.Dynamic servlet = ctx.addServlet( HelloWorldServlet.class.getSimpleName(), HelloWorldServlet.class); servlet.addMapping("/hello"); System.out.println("建立 Hello World Filter..."); javax.servlet.FilterRegistration.Dynamic filter = ctx.addFilter(HelloWorldFilter.class.getSimpleName(), HelloWorldFilter.class); EnumSet<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class); dispatcherTypes.add(DispatcherType.REQUEST); dispatcherTypes.add(DispatcherType.FORWARD); filter.addMappingForUrlPatterns(dispatcherTypes, true, "/hello"); } }
虽然 ServletCantainerInitializer 不能被内嵌容器加载,ServletContextInitializer 却能被 SpringBoot 的 ServletWebServerApplicationContext 加载到,从而装配其中的 Servlet 和 Filter。实际开发中,仍是以一,二两种方式来注册为主,这里只是提供一个可能性,来让咱们理解 SpringBoot 的加载流程
天然是被 new
出来的,在 TomcatServletWebServerFactory#configureContext
中能够看到,TomcatStarter 是被主动实例化出来的,而且还传入了 ServletContextInitializer 的数组,和上面分析的同样,一共有三个 ServletContextInitializer,包含了 ServletWebServerApplicationContext 中的匿名实现
protected void configureContext(Context context, ServletContextInitializer[] initializers) { // <1> TomcatStarter starter = new TomcatStarter(initializers); // <2> if (context instanceof TomcatEmbeddedContext) { // Should be true ((TomcatEmbeddedContext) context).setStarter(starter); } // ... 省略相关代码 }
<1>
处,建立了 TomcatStarter 对象。<2>
处,经过 context instanceof TomcatEmbeddedContext
判断使用的是内嵌的 Tomcat ,因此将 TomcatStarter 做为 Initializer若是对 <2>
处的逻辑感兴趣的胖友,能够在如下方法上打断点进行调试
TomcatServletWebServerFactory#getWebServer(ServletContextInitializer... initializers)
TomcatStarter#onStartup(Set<Class<?>> classes, ServletContext servletContext)
ServletWebServerApplicationContext#createWebServer
执行顺序:三、一、2
@Configuration @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class) @ConditionalOnWebApplication(type = Type.SERVLET) // 这个就是咱们 SpringBoot 中 application.yml 配置文件中 server.* 配置类,也就是 Tomcat 相关配置 @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 // 保证存在 Tomcat 的 Class 对象 @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer( ServerProperties serverProperties) { return new TomcatServletWebServerFactoryCustomizer(serverProperties); } // 省略 WebServerFactoryCustomizerBeanPostProcessor 类 }
其中 @Import
注解会注入 ServletWebServerFactoryConfiguration
的几个静态内部类,以下:
class ServletWebServerFactoryConfiguration { @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(); } } // 省略 EmbeddedJetty、EmbeddedUndertow }
这样一来,只要 classpath 下存在 javax.servlet.Servlet
、org.apache.catalina.startup.Tomcat
、org.apache.coyote.UpgradeProtocol
类,而且不存在 ServletWebServerFactory
类型的 Bean 则会注入 EmbeddedTomcat 配置类,也就建立一个 TomcatServletWebServerFactory 类型的 Bean
存在 web.xml
配置的 Java Web 项目,Servlet3.0 的 Java Web 项目,Spring Boot 内嵌容器的 Java Web 项目加载 Servlet,这三种项目,Servlet,Filter,Listener 的流程都是有所差别的。理解清楚这其中的由来,其实并不容易,至少得搞懂 Servlet3.0 的规范,SpringBoot 内嵌容器的加载流程等等前置逻辑
简化了整个 SpringBoot 加载 Servlet 的流程,以下图所示:
参考文章:芋道源码《精尽 MyBatis 源码分析》