1.基于上帝视角的分析
在上一篇中,分析了springSecurityFilterChain
是在哪进行进行创建的,以及如何将这个bean注入到spring 容器中去。最后发现是通过spring-boot-autoconfigure.jar
包中的org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
来完成的。
springSecurityFilterChain
这个在哪加入到servlet容器中的,其实比较难找,我差不多通过一个小时的调试,最终才发现。
先公布成果:
向容器注入了一个DelegatingFilterProxyRegistrationBean
,然后ServletWebServerApplicationContext将这个DelegatingFilterProxyRegistrationBean
解析出来,
加入到servletContext中,这样完成了springSecurityFilterChain
注册成为servlet容器的过滤器的过程。
下面我们将从tomcat到DelegatingFilterProxyRegistrationBean
逐步解析。
2.spring 启动之tomcat服务器创建
- 在springboot启动时,会通过
ServletWebServerApplicationContext
创建一个web服务器
- getSelfInitializer() 灵魂用法
这里通过函数式编程,将selfInitialize
方法注入到ServletContextInitializer
这里的
this::selfInitialize
通过lambda创建一个匿名内部类,然后ServletContextInitializer
的onStartUp方法在底层就自动改为调用selfInitialize
方法了。
这个李代桃僵差点给我看懵了,需要学习的地方还有很多啊。
这个方法也很关键,后面真正进行filter注册,也是在这个方法中。
3.tomcat四大容器之StandardContext启动
在StandardContext
中进行TomcatStarter.onStartup()
的调用。这个就意味着基于tomcat的servlet容器初始化开始了。
4. 内置Tomcat之TocatStarter执行ServletContainerInitializer启动
从上图我们可以看到,在三个ServletContainerInitializer
中,有一个ServletWebServerApplicationContext$lambda
对象。
这个意味着,这是一个在ServletWebServerApplicationContext
中创建的匿名lambda对象,而且这个对象是ServletContainerInitializer
的子类。
那这里的initializer.onStartup(servletContext);
其实就是调用的ServletWebServerApplicationContext
的selfInitialize
方法。这个跟本篇的第二点不谋而合。
那最终,我们需要关注的就是selfInitialize
函数。
5. selfInitialize 做了些啥?
private void selfInitialize(ServletContext servletContext) throws ServletException {
// 1.判断servletContext是否重复初始化
// 2.servletContext 交给ServletWebServerApplicationContext管理的方法
prepareWebApplicationContext(servletContext);
// 1.设置sevletContext的作用域
registerApplicationScope(servletContext);
// 注册servletContext单例到spring容器中
// 注册servletConfig单例到spring 容器中
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 获取 ServletContextInitializer 子类,然后依次执行onStartup
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
上文中主要可以分为两步:
1.servletContext的管理
2.ServletContextInitializer 继续初始化servletContext
这里我们要重点看看 getServletContextInitializerBeans()
有哪些ServletContextInitializer对象。
6.getServletContextInitializerBeans() 获取ServletContextInitializer 对象集合
那么首先看看addServletContextInitializerBeans
方法
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
// 这个是针对多个初始化类型进行Bean获取操作
for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
// 通过initializerType 获取spring容器中的ServletContextInitializer实例
for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
initializerType)) {
// 对这里ServletContextInitializer的实例进行具体操作
addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
}
}
}
基于ServletContextInitializer
类型去获取实例列表
private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type,
Set<?> excludes) {
// 先获取实例别名
String[] names = beanFactory.getBeanNamesForType(type, true, false);
Map<String, T> map = new LinkedHashMap<>();
for (String name : names) {
// 1.是否在排除列表中 2.是否在作用域范围内
if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
// 获取具体的bean
T bean = beanFactory.getBean(name, type);
if (!excludes.contains(bean)) {
map.put(name, bean);
}
}
}
List<Entry<String, T>> beans = new ArrayList<>(map.entrySet());
// 在对这些ServletContextInitializer根据@Order注解进行排序
beans.sort((o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(), o2.getValue()));
return beans;
}
看到没,securityFilterChainRegistration -> {DelegatingFilterProxyRegistrationBean@7429} "springSecurityFilterChain urls=[/*] order=-100"
这个ServletContextInitializer 的实例,就是我们之前在SecurityFilterAutoConfiguration中发现的DelegatingFilterProxyRegistrationBean
从SecurityFilterAutoConfiguration
到 Servlet的容器的联动,基本都是通过ServletContextInitializer
来完成的。
体悟:
所以当我们想要开发一个基于spring mvc的过滤器插件时,就可以考虑使用继承AbstractFilterRegistrationBean
来写自己的过滤器入口,然后注入spring容器即可~
现在还有最后一步,如何将获取的这些过滤器注册到servlet容器中去呢?
7. RegistrationBean#onStartup之ServletContextInitializer 注册到servletContext
在通过getOrderedBeansOfType
获取到ServletContextInitializer
实例列表后,我们需要回到ServletWebServerApplicationContext
中,去执行每个ServletContextInitializer
实例的onStartup
方法。
8.小结
在这个源码探索流程中,debug和大胆猜想很重要,当然经验也让我能更快的去探索到对应的内容。
本篇的分析得到的结论:
springSecurityFilterChain
是在SecurityFilterAutoConfiguration
被注入到spring容器中- 内置tomcat的
TomcatStarter.onStartup
方法实现了在tomcat启动中对ServletWebServerApplicationContext#selfInitialize
方法的回调 - selfInitialize 完成了ServletContextInitializer服务实例的获取和过滤器注册到servlet容器