Spring Security(六)—SpringSecurityFilterChain加载流程深度解析

SpringSecurityFilterChain 作为 SpringSecurity 的核心过滤器链在整个认证授权过程中起着举足轻重的地位,每个请求到来,都会经过该过滤器链,前文《Spring Security(四)–核心过滤器源码分析》 中我们分析了 SpringSecurityFilterChain 的构成,但还有很多疑问可能没有解开:

  1. 这个 SpringSecurityFilterChain 是怎么注册到 web 环境中的?
  2. 有读者发出这样的疑问:”SpringSecurityFilterChain 的实现类到底是什么,我知道它是一个 Filter,但是在很多配置类中看到了 BeanName=SpringSecurityFilterChain 相关的类,比如 DelegatingFilterProxy,FilterChainProxy,SecurityFilterChain,他们的的名称实在太相似了,到底哪个才是真正的实现,SpringSecurity 又为什么要这么设计?“
  3. 我们貌似一直在配置 WebSecurity ,但没有对 SpringSecurityFilterChain 进行什么配置,WebSecurity 相关配置是怎么和 SpringSecurityFilterChain 结合在一起的?

以上是个人 YY 的一些 SpringSecurityFilterChain 相关的问题,因为我当初研究了一段时间 SpringSecurity 源码,依旧没有理清这么多错综复杂的类。那么本文就主要围绕 SpringSecurityFilterChain 展开我们的探索。

###6.1 SpringSecurityFilterChain是怎么注册的?

这个问题并不容易解释,因为 SpringSecurity 仅仅在 web 环境下(SpringSecurity 还支持非 web 环境)就有非常多的支持形式:

Java 配置方式

  1. 作为独立的 SpringSecurity 依赖提供给朴素的 java web 项目使用,并且项目不使用 Spring!没错,仅仅使用 servlet,jsp 的情况下也是可以集成 SpringSecurity 的。
  2. 提供给包含 SpringMVC 项目使用。
  3. 提供给具备 Servlet3.0+ 的 web 项目使用。
  4. SpringBoot 内嵌容器环境下使用 SpringSecurity,并且包含了一定程度的自动配置。

XML 配置方式

  1. 使用 XML 中的命名空间配置 SpringSecurity。

注意,以上条件可能存在交集,比如我的项目是一个使用 servlet3.0 的 web 项目同时使用了 SpringMVC;也有可能使用了 SpringBoot 同时配合 SpringMVC;还有可能使用了 SpringBoot,却打成了 war 包,部署在外置的支持 Servlet3.0+ 规范的应用容器中…各种组合方式会导致配置 SpringSecurityFilterChain 的注册方式产生差异,所以,这个问题说复杂还真有点,需要根据你的环境来分析。我主要分析几种较为常见的注册方式。

SpringSecurityFilterChain 抽象概念里最重要的三个类:DelegatingFilterProxy,FilterChainProxy 和 SecurityFilterChain,对这三个类的源码分析和设计将会贯彻本文。不同环境下 DelegatingFilterProxy 的注册方式区别较大,但 FilterChainProxy 和 SecurityFilterChain 的差异不大,所以重点就是分析 DelegatingFilterProxy 的注册方式。它们三者的分析会放到下一节中。

####6.1.1 servlet3.0+环境下SpringSecurity的java config方式

这是一个比较常见的场景,你可能还没有使用 SpringBoot 内嵌的容器,将项目打成 war 包部署在外置的应用容器中,比如最常见的 tomcat,一般很少 web 项目低于 servlet3.0 版本的,并且该场景摒弃了 XML 配置。

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
	extends AbstractSecurityWebApplicationInitializer {

}

主要自定义一个 SecurityWebApplicationInitializer 并且让其继承自 AbstractSecurityWebApplicationInitializer 即可。如此简单的一个继承背后又经历了 Spring 怎样的封装呢?自然要去 AbstractSecurityWebApplicationInitializer 中去一探究竟。经过删减后的源码如下

public abstract class AbstractSecurityWebApplicationInitializer
      implements WebApplicationInitializer {//<1>

   public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";

   // <1> 父类WebApplicationInitializer的加载入口
   public final void onStartup(ServletContext servletContext) throws ServletException {
      beforeSpringSecurityFilterChain(servletContext);
      if (this.configurationClasses != null) {
         AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
         rootAppContext.register(this.configurationClasses);
         servletContext.addListener(new ContextLoaderListener(rootAppContext));
      }
      if (enableHttpSessionEventPublisher()) {
         servletContext.addListener(
               "org.springframework.security.web.session.HttpSessionEventPublisher");
      }
      servletContext.setSessionTrackingModes(getSessionTrackingModes());
      insertSpringSecurityFilterChain(servletContext);//<2>
      afterSpringSecurityFilterChain(servletContext);
   }
   
    // <2> 在这儿初始化了关键的DelegatingFilterProxy
    private void insertSpringSecurityFilterChain(ServletContext servletContext) {
		String filterName = DEFAULT_FILTER_NAME;
        // <2> 该方法中最关键的一个步骤,DelegatingFilterProxy在此被创建
		DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
				filterName);
		String contextAttribute = getWebApplicationContextAttribute();
		if (contextAttribute != null) {
			springSecurityFilterChain.setContextAttribute(contextAttribute);
		}
		registerFilter(servletContext, true, filterName, springSecurityFilterChain);
	}
    
    // <3> 使用servlet3.0的新特性,动态注册springSecurityFilterChain(实际上注册的是springSecurityFilterChain代理类)
    private final void registerFilter(ServletContext servletContext,
			boolean insertBeforeOtherFilters, String filterName, Filter filter) {
		Dynamic registration = servletContext.addFilter(filterName, filter);
		registration.setAsyncSupported(isAsyncSecuritySupported());
		EnumSet<DispatcherType> dispatcherTypes = getSecurityDispatcherTypes();
		registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters,
				"/*");
	}

}

<1><3> 放在一起讲,因为他们都和 servlet3.0 新特性以及 spring 对 servlet3.0 的支持相关,这也是为什么在场景描述中我特地强调了需要 servlet3.0 环境。如果你对 servlet3.0 的新特性不了解,这儿准备了一篇详细的介绍为你阐述《Spring揭秘–寻找遗失的web.xml》 。得益于 Spring 的封装,在 servlet3.0 环境下,web 容器启动时会自行去寻找类路径下所有实现了 WebApplicationInitializer 接口的 Initializer 实例,并调用他们的 onStartup 方法。所以,我们只需要继承 AbstractSecurityWebApplicationInitializer ,便可以自动触发 web 容器的加载,进而配置和 SpringSecurityFilterChain 第一个密切相关的类,第<2>步中的 DelegatingFilterProxy。

<2> DelegatingFilterProxy 在此被实例化出来。在第<3>步中,它作为一个 Filter 正式注册到了 web 容器中。

6.1.2 XML 配置

这个真的是简单易懂,因为它是被指名道姓配置成一个 Filter 的。

web.xml

<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

web.xml 的存在注定了其无所谓当前环境是不是 servlet3.0+,虽然我个人不太喜欢 xml 的配置方式,但不得不说,这样真的很简单粗暴。

6.1.3 SpringBoot 内嵌应用容器并且使用自动配置

《Spring揭秘–寻找遗失的web.xml》中我曾经得出一个结论,内嵌容器是完全不会使用 SPI 机制加载 servlet3.0 新特性的那些 Initializer 的,springboot 又推崇 java configuration,所以上述两种方案完全被抛弃了。那么 SpringBoot 如何注册 DelegatingFilterProxy 呢?

 

@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class,
      SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {

   private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;//springSecurityFilterChain

    // <1>
   @Bean
   @ConditionalOnBean(name = DEFAULT_FILTER_NAME)
   public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
         SecurityProperties securityProperties) {
      DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
            DEFAULT_FILTER_NAME);
      registration.setOrder(securityProperties.getFilterOrder());
      registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
      return registration;
   }

   @Bean
   @ConditionalOnMissingBean
   public SecurityProperties securityProperties() {
      return new SecurityProperties();
   }
}

<1> DelegatingFilterProxyRegistrationBean 的分析在之前那篇文章中也有详细的介绍,其作用便是在 SpringBoot 环境下通过 TomcatStarter 等内嵌容器启动类来注册一个 DelegatingFilterProxy。这下,和前面两种配置方式都对应上了。

###SpringSecurityFilterChain三个核心类的源码分析

理解 SpringSecurityFilterChain 的工作流程必须搞懂三个类org.springframework.web.filter.DelegatingFilterProxyorg.springframework.security.web.FilterChainProxy 

 org.springframework.security.web.SecurityFilterChain

DelegatingFilterProxy

上面一节主要就是介绍 DelegatingFilterProxy 在不同环境下的注册方式,可以很明显的发现,DelegatingFilterProxy 是 SpringSecurity 的“门面”,注意它的包结构:org.springframework.web.filter,它本身是 Spring Web 包中的类,并不是 SpringSecurity 中的类。因为 Spring 考虑到了多种使用场景,自然希望将侵入性降到最低,所以使用了这个委托代理类来代理真正的 SpringSecurityFilterChain。DelegatingFilterProxy 实现了 javax.servlet.Filter 接口,使得它可以作为一个 java web 的标准过滤器,其职责也很简单,只负责调用真正的 SpringSecurityFilterChain。

删减掉非重要代码后的 DelegatingFilterProxy:

public class DelegatingFilterProxy extends GenericFilterBean {

   private WebApplicationContext webApplicationContext;
   // springSecurityFilterChain
   private String targetBeanName;
   // <1> 关键点
   private volatile Filter delegate;
   private final Object delegateMonitor = new Object();

   public DelegatingFilterProxy(String targetBeanName, WebApplicationContext wac) {
      Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
      this.setTargetBeanName(targetBeanName);
      this.webApplicationContext = wac;
      if (wac != null) {
         this.setEnvironment(wac.getEnvironment());
      }
   }

   @Override
   protected void initFilterBean() throws ServletException {
      synchronized (this.delegateMonitor) {
         if (this.delegate == null) {
            if (this.targetBeanName == null) {
               this.targetBeanName = getFilterName();
            }
            // Fetch Spring root application context and initialize the delegate early,
            // if possible. If the root application context will be started after this
            // filter proxy, we'll have to resort to lazy initialization.
            WebApplicationContext wac = findWebApplicationContext();
            if (wac != null) {
               this.delegate = initDelegate(wac);
            }
         }
      }
   }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
         throws ServletException, IOException {

      // 过滤器代理支持懒加载
      Filter delegateToUse = this.delegate;
      if (delegateToUse == null) {
         synchronized (this.delegateMonitor) {
            delegateToUse = this.delegate;
            if (delegateToUse == null) {
               WebApplicationContext wac = findWebApplicationContext();
               delegateToUse = initDelegate(wac);
            }
            this.delegate = delegateToUse;
         }
      }

      // 让代理过滤器执行实际的过滤行为
      invokeDelegate(delegateToUse, request, response, filterChain);
   }

   // 初始化过滤器代理
   // <2>
   protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
      Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
      if (isTargetFilterLifecycle()) {
         delegate.init(getFilterConfig());
      }
      return delegate;
   }


   // 调用代理过滤器	
   protected void invokeDelegate(
         Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
         throws ServletException, IOException {
      delegate.doFilter(request, response, filterChain);
   }

}

ServletRegistrationBean 和 FilterRegistrationBean 都集成自 RegistrationBean ,RegistrationBean 是 springboot 中广泛应用的一个注册类,负责把 servlet,filter,listener 给容器化,使他们被 spring 托管,并且完成自身对 web 容器的注册。这种注册方式也值得推崇

 

RegistrationBean

从图中可以看出 RegistrationBean 的地位,它的几个实现类作用分别是:帮助容器注册 filter,servlet,listener,最后的 DelegatingFilterProxyRegistrationBean 使用的不多,但熟悉 SpringSecurity 的朋友不会感到陌生,SpringSecurityFilterChain 就是通过这个代理类来调用的。另外 RegistrationBean 实现了 ServletContextInitializer 接口,这个接口将会是下面分析的核心接口,大家先混个眼熟,了解下它有一个抽象实现 RegistrationBean 即可。

SpringBoot中servlet加载流程的源码分析

暂时只介绍这两种方式,下面解释下之前卖的关子,为什么说 springboot 没有完全遵守 servlet3.0 规范。讨论的前提是 springboot 环境下使用内嵌的容器,比如最典型的 tomcat。高能预警,以下内容比较烧脑,觉得看起来吃力的朋友可以跳过本节直接看下一节的总结!

Initializer被替换为TomcatStarter

当使用内嵌的 tomcat 时,你会发现 springboot 完全走了另一套初始化流程,完全没有使用前面提到的 SpringServletContainerInitializer,实际上一开始我在各种 ServletContainerInitializer 的实现类中打了断点,最终定位到,根本没有运行到 SpringServletContainerInitializer 内部,而是进入了 TomcatStarter 这个类中。

 

TomcatStarter

并且,仔细扫了一眼源码的包,并没有发现有 SPI 文件对应到 TomcatStarter。于是我猜想,内嵌 tomcat 的加载可能不依赖于 servlet3.0 规范和 SPI!它完全走了一套独立的逻辑。为了验证这一点,我翻阅了 spring github 中的 issue,得到了 spring 作者肯定的答复:https://github.com/spring-projects/spring-boot/issues/321

 

springboot 这么做是有意而为之。springboot 考虑到了如下的问题,我们在使用 springboot 时,开发阶段一般都是使用内嵌 tomcat 容器,但部署时却存在两种选择:一种是打成 jar 包,使用 java -jar 的方式运行;另一种是打成 war 包,交给外置容器去运行。前者就会导致容器搜索算法出现问题,因为这是 jar 包的运行策略,不会按照 servlet3.0 的策略去加载 ServletContainerInitializer!最后作者还提供了一个替代选项:ServletContextInitializer,注意是 ServletContextInitializer!它和 ServletContainerInitializer 长得特别像,别搞混淆了,前者 ServletContextInitializer 是 org.springframework.boot.web.servlet.ServletContextInitializer,后者 ServletContainerInitializer 是 javax.servlet.ServletContainerInitializer,前文还提到 RegistrationBean 实现了 ServletContextInitializer 接口。

TomcatStarter中的ServletContextInitializer是关键

TomcatStarter 中的 org.springframework.boot.context.embedded.ServletContextInitializer 是 springboot 初始化 servlet,filter,listener 的关键。

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);
         }
   }
}

经过删减源码后,可以看出 TomcatStarter 的主要逻辑,它其实就是负责调用一系列 ServletContextInitializer 的 onStartup 方法,那么在 debug 中,ServletContextInitializer[] initializers 到底包含了哪些类呢?会不会有我们前面介绍的 RegisterBean 呢?

太天真了,RegisterBean 并没有出现在 TomcatStarter 的 debug 信息中,initializers 只包含了三个类,其中只有第一个类看上去比较核心,注意第一个类不是 EmbeddedWebApplicationContext!而是这个类中的 $1 匿名类,为了搞清楚 springboot 如何加载 filter servlet listener ,看来还得研究下 EmbeddedWebApplicationContext 的结构。

EmbeddedWebApplicationContext中的6层迭代加载

ApplicationContext 大家应该是比较熟悉的,这是 spring 一个比较核心的类,一般我们可以从中获取到那些注册在容器中的托管 Bean,而这篇文章,主要分析的便是它在内嵌容器中的实现类:EmbeddedWebApplicationContext,重点分析它加载 filter servlet listener 这部分的代码。这里是整个代码中迭代层次最深的部分,做好心理准备起航,来看看 EmbeddedWebApplicationContext 是怎么获取到所有的 servlet filter listener 的!以下方法均出自于 EmbeddedWebApplicationContext。

第一层:onRefresh()

onRefresh 是 ApplicationContext 的生命周期方法,EmbeddedWebApplicationContext 的实现非常简单,只干了一件事:

@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 的总称。

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();
}

凡是带有 servlet,initializer 字样的方法都是我们需要留意的,getSelfInitializer() 便涉及到了我们最为关心的初始化流程。

第三层:getSelfInitializer()

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);
   }
}

还记得前面 TomcatStarter 的 debug 信息中,第一个 ServletContextInitializer 就是出现在 EmbeddedWebApplicationContext 中的一个匿名类,没错了,就是这里的 getSelfInitializer() 方法创建的!解释下这里的 getSelfInitializer() 和 selfInitialize(ServletContext servletContext) 为什么要这么设计:这是典型的回调式方式,当匿名 ServletContextInitializer 类被 TomcatStarter 的 onStartup 方法调用,设计上是触发了 selfInitialize(ServletContext servletContext) 的调用。所以这下就清晰了,为什么 TomcatStarter 中没有出现 RegisterBean ,其实是隐式触发了 EmbeddedWebApplicationContext 中的 selfInitialize 方法。selfInitialize 方法中的 getServletContextInitializerBeans() 成了关键。

第四层:getServletContextInitializerBeans()

/**
 * 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的构造方法

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)

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
   for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
         beanFactory, ServletContextInitializer.class)) {
      addServletContextInitializerBean(initializerBean.getKey(),
            initializerBean.getValue(), beanFactory);
   }
}

getOrderedBeansOfType 方法便是去容器中寻找注册过得 ServletContextInitializer ,这时候就可以把之前那些 RegisterBean 全部加载出来了,并且 RegisterBean 还实现了 Ordered 接口,在这儿用于排序。不再往下迭代了。

EmbeddedWebApplicationContext加载流程总结

如果你对具体的代码流程不感兴趣,可以跳过上述的6层分析,直接看本节的结论。总结如下:

  • EmbeddedWebApplicationContext 的 onRefresh 方法触发配置了一个匿名的 ServletContextInitializer。
  • 这个匿名的 ServletContextInitializer 的 onStartup 方法会去容器中搜索到了所有的 RegisterBean 并按照顺序加载到 ServletContext 中。
  • 这个匿名的 ServletContextInitializer 最终传递给 TomcatStarter,由 TomcatStarter 的 onStartup 方法去触发 ServletContextInitializer 的 onStartup 方法,最终完成装配!

第三种注册 Servlet 的方式

研究完了上述 springboot 启动的内部原理,可以发现 ServletContextInitializer 其实是 spring 中 ServletContainerInitializer 的代理,虽然 springboot 中 Servlet3.0 不起作用了,但它的代理还是会被加载的,于是我们有了第三种方式注册 servlet。

@Configuration
public class CustomServletContextInitializer implements ServletContextInitializer {

    private final static String JAR_HELLO_URL = "/hello";

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println("创建 helloWorldServlet...");

        ServletRegistration.Dynamic servlet = servletContext.addServlet(
                HelloWorldServlet.class.getSimpleName(),
                HelloWorldServlet.class);
        servlet.addMapping(JAR_HELLO_URL);

        System.out.println("创建 helloWorldFilter...");

        FilterRegistration.Dynamic filter = servletContext.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, JAR_HELLO_URL);
    }
}

虽然 ServletCantainerInitializer 不能被内嵌容器加载,ServletContextInitializer 却能被 springboot 的 EmbeddedWebApplicationContext 加载到,从而装配其中的 servlet 和 filter。实际开发中,还是以一,二两种方法来注册为主,这里只是提供一个可能性,来让我们理解 springboot 的加载流程。

加载流程拾遗

  1. TomcatStarter 既然不是通过 SPI 机制装配的,那是怎么被 spring 使用的?

自然是被 new 出来的,在 TomcatEmbeddedServletContainerFactory#configureContext 中可以看到,TomcatStarter 是被主动实例化出来的,并且还传入了 ServletContextInitializer 的数组,和上面分析的一样,一共有三个 ServletContextInitializer,包含了 EmbeddedWebApplicationContext 中的匿名实现。

protected void configureContext(Context context,
      ServletContextInitializer[] initializers) {
   TomcatStarter starter = new TomcatStarter(initializers);
   if (context instanceof TomcatEmbeddedContext) {
      // Should be true
      ((TomcatEmbeddedContext) context).setStarter(starter);
   }
   context.addServletContainerInitializer(starter, NO_CLASSES);
   ...
   }
}
  1. TomcatEmbeddedServletContainerFactory 又是如何被声明的?
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {

   /**
    * Nested configuration if Tomcat is being used.
    */
   @Configuration
   @ConditionalOnClass({ Servlet.class, Tomcat.class })
   @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
   public static class EmbeddedTomcat {

      @Bean
      public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
         return new TomcatEmbeddedServletContainerFactory();
      }

   }
}

 

只要类路径下存在 Tomcat 类,以及在 web 环境下,就会触发 springboot 的自动配置。

总结

存在 web.xml 配置的 java web 项目,servlet3.0 的 java web 项目,springboot 内嵌容器的 java web 项目加载 servlet,filter,listener 的流程都是有所差异的,理解清楚这其中的原来,其实并不容易,至少得搞懂 servlet3.0 的规范,springboot 内嵌容器的加载流程等等前置逻辑。

最后感谢下小马哥的点拨,在此之前误以为: TomcatStarter 既然继承了 ServletContainerInitializer,应该也是符合 servlet3.0 规范的,但实际上并没有被 SPI 加载。

推荐阅读

JAVA拾遗–关于SPI机制 https://www.cnkirito.moe/spi/

 

 

 

 

 

 

本文作者:徐靖峰
原文链接:https://www.cnkirito.moe/2017/10/01/spring-security-5/
版权归作者所有,转载请注明出处

 

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值