Spring Boot 对于 servlet 的处理才是重头戏:
其一,是因为 Spring Boot 使用范围很广,很少有人用 spring 而不用 Spring Boot 了
其二,是因为它没有完全遵守 Servlet3.0 的规范!
Spring Boot 依旧兼容 Servlet 3.0 一系列以 @Web* 开头的注解:@WebServlet,@WebFilter,@WebListener
1 2 3 4 @WebServlet("/hello") public class HelloWorldServlet extends HttpServlet{} @WebFilter("/hello/*") public class HelloWorldFilter implements Filter {}
不要忘记让启动类去扫描到这些注解
1 2 3 4 5 6 7 8 @SpringBootApplication @ServletComponentScan public class SpringBootServletApplication { public static void main(String[] args) { SpringApplication.run(SpringBootServletApplication.class, args); } }
如果真的有特殊需求,需要在 Spring Boot 下注册 servlet,filter,可以采用这样的方式,比较直观。
注册方式二:RegistrationBean 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Bean public ServletRegistrationBean helloWorldServlet() { ServletRegistrationBean helloWorldServlet = new ServletRegistrationBean(); myServlet.addUrlMappings("/hello"); myServlet.setServlet(new HelloWorldServlet()); return helloWorldServlet; } @Bean public FilterRegistrationBean helloWorldFilter() { FilterRegistrationBean helloWorldFilter = new FilterRegistrationBean(); myFilter.addUrlPatterns("/hello/*"); myFilter.setFilter(new HelloWorldFilter()); return helloWorldFilter; }
ServletRegistrationBean 和 FilterRegistrationBean 都集成自 RegistrationBean ,RegistrationBean 是 Spring Boot 中广泛应用的一个注册类,负责把 servlet,filter,listener 给容器化,使他们被 Spring 托管,并且完成自身对 Web 容器的注册。这种注册方式也值得推崇。
前三个,帮助容器注册 filter,servlet,listener
最后的 DelegatingFilterProxyRegistrationBean 使用的不多,但熟悉 Spring Security 的朋友不会感到陌生,SpringSecurityFilterChain 就是通过这个代理类来调用的。
SpringBoot 中 Servlet 加载流程的源码分析
Initializer 被替换为 TomcatStarter 当使用内嵌的 Tomcat 时,你会发现 Spring Boot 完全走了另一套初始化流程,完全没有使用前面提到的 SpringServletContainerInitializer,而是进入了 org.springframework.boot.web.embedded.tomcat.TomcatStarter 这个类中 内嵌 Tomcat 的加载不依赖于 Servlet3.0 规范和 SPI !它完全走了一套独立的逻辑。
我们在使用 Spring Boot 时,开发阶段一般都是使用内嵌 Tomcat 容器,但部署时却存在两种选择:
一种是打成 jar 包,使用 java -jar 的方式运行
另一种是打成 war 包,交给外置容器去运行。
前者就会导致容器搜索算法出现问题,因为这是 jar 包的运行策略,不会按照 Servlet 3.0 的策略去加载 ServletContainerInitializer!
TomcatStarter 中的 ServletContextInitializer 是关键 TomcatStarter 中 org.springframework.boot.context.embedded.ServletContextInitializer[] initializers 属性,是 Spring Boot 初始化 servlet,filter,listener 的关键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } } }
initializers 只包含了三个类,其中只有第一个类看上去比较核心,注意第一个类不是 EmbeddedWebApplicationContext !而是这个类中的 $1 匿名类,为了搞清楚 Spring Boot 如何加载 filter、servlet、listener ,看来还得研究下 EmbeddedWebApplicationContext 的结构
EmbeddedWebApplicationContext 中的 6 层迭代加载
第一层:onRefresh() ApplicationContext 的生命周期方法,EmbeddedWebApplicationContext 的实现非常简单,只干了一件事:
1 2 3 4 5 6 7 8 9 10 @Override protected void onRefresh() { super.onRefresh(); try { createEmbeddedServletContainer(); //第二层的入口 } catch (Throwable ex) { throw new ApplicationContextException("Unable to start embedded container", ex); } }
调用 #createEmbeddedServletContainer() 方法,连接到了第二层
第二层:createEmbeddedServletContainer() 看名字 Spring 是想创建一个内嵌的 Servlet 容器,ServletContainer 其实就是 servlet、filter、listener 的总称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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(); }
#getSelfInitializer() 方法,便涉及到了我们最为关心的初始化流程,所以接着连接到了第三层。
第三层:getSelfInitializer() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 private org.springframework.boot.web.servlet.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); } }
EmbeddedWebApplicationContext 中的匿名类,没错了,就是这里的 #getSelfInitializer() 方法创建的!
为什么要这么设计? 这是典型的回调式方式,当匿名 ServletContextInitializer 类被 TomcatStarter 的 #onStartup() 方法调用,设计上是触发了 #selfInitialize(ServletContext servletContext) 方法的调用。
为什么 TomcatStarter 中没有出现 RegistrationBean 其实是隐式触发了 EmbeddedWebApplicationContext 中的 #selfInitialize(ServletContext servletContext) 方法。这样,#selfInitialize(ServletContext servletContext) 方法中,调用 #getServletContextInitializerBeans() 方法
获得 ServletContextInitializer 数组就成了关键。�所以接着连接到了第四层。
第四层:getServletContextInitializerBeans() 1 2 3 4 5 6 7 8 9 10 /** * Returns {@link ServletContextInitializer}s that should be used with the embedded * Servlet context. 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 的
第五层:ServletContextInitializerBeans 的构造方法 1 2 3 4 5 6 7 8 9 10 11 12 public ServletContextInitializerBeans(ListableBeanFactory beanFactory) { this.initializers = new LinkedMultiValueMap<Class<?>, ServletContextInitializer>(); addServletContextInitializerBeans(beanFactory); // 第六层的入口 addAdaptableBeans(beanFactory); List<ServletContextInitializer> sortedInitializers = new ArrayList<ServletContextInitializer>(); for (Map.Entry<?, List<ServletContextInitializer>> entry : this.initializers .entrySet()) { AnnotationAwareOrderComparator.sort(entry.getValue()); sortedInitializers.addAll(entry.getValue()); } this.sortedList = Collections.unmodifiableList(sortedInitializers); }
第六层:addServletContextInitializerBeans(beanFactory) 1 2 3 4 5 6 7 8 9 // 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) 方法,便是去容器中寻找注册过得 ServletContextInitializer ,这时候就可以把之前那些 RegistrationBean 全部加载出来了。并且 RegistrationBean 还实现了 Ordered 接口,在这儿用于排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // ServletContextInitializerBeans.java private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) { for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType( beanFactory, ServletContextInitializer.class)) { addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory); } } private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) { if (initializer instanceof ServletRegistrationBean) { Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet(); addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source); } else if (initializer instanceof FilterRegistrationBean) { Filter source = ((FilterRegistrationBean<?>) initializer).getFilter(); addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source); } else if (initializer instanceof DelegatingFilterProxyRegistrationBean) { String source = ((DelegatingFilterProxyRegistrationBean) initializer) .getTargetBeanName(); addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source); } else if (initializer instanceof ServletListenerRegistrationBean) { EventListener source = ((ServletListenerRegistrationBean<?>) initializer) .getListener(); addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source); } else { addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer); } }
各种 RegistrationBean 的处理
EmbeddedWebApplicationContext加载流程总结
EmbeddedWebApplicationContext 的 #onRefresh() 方法,触发配置了一个匿名的 ServletContextInitializer
这个匿名的 ServletContextInitializer 的 onStartup(ServletContext servletContext) 方法,会去容器中搜索到了所有的 RegistrationBean ,并按照顺序加载到 ServletContext 中。
这个匿名的 ServletContextInitializer 最终传递给 TomcatStarter,由 TomcatStarter 的 onStartup 方法去触发 ServletContextInitializer 的 #onStartup(ServletContext servletContext) 方法,最终完成装配!
加载流程拾遗
TomcatStarter 既然不是通过 SPI 机制装配的,那是怎么被 Spring 使用的? 自然是被 new 出来的。在 TomcatEmbeddedServletContainerFactory#configureContext(Context context, ServletContextInitializer[] initializers) 方法中可以看到,TomcatStarter 是被主动实例化出来的,并且还传入了 ServletContextInitializer 的数组,和上面分析的一样,一共有三个 ServletContextInitializer,包含了 EmbeddedWebApplicationContext 中的匿名实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // TomcatEmbeddedServletContainerFactory.java 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); } context.addServletContainerInitializer(starter, NO_CLASSES); // ... 省略无关代码 }
通过 context instanceof TomcatEmbeddedContext 判断使用的是内嵌的 Tomcat
SpringServletContainerInitializer 替换成了 TomcatStarter 类。
TomcatEmbeddedServletContainerFactory 又是如何被声明的? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // EmbeddedServletContainerAutoConfiguration.java @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration @ConditionalOnWebApplication // web 环境下 @Import(BeanPostProcessorsRegistrar.class) public class EmbeddedServletContainerAutoConfiguration { /** * Nested configuration if Tomcat is being used. */ @Configuration @ConditionalOnClass({ Servlet.class, Tomcat.class }) // 不能再 Servlet + Tomcat 类 @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat { @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { return new TomcatEmbeddedServletContainerFactory(); // TomcatEmbeddedServletContainerFactory 对象 } } // 省略 EmbeddedJetty // 省略 EmbeddedUndertow }
总结