SpringBoot和Spring-web的容器分析
在这篇开始之前,建议先看从Servlet和Spring-web整合理解父子容器
编码方式实现Web.xml配置
通过上一篇的文章知道了,要实现Servlet和Spring-web融合,就需要在web.xml中主要配置两个东西
- ContextLoaderListener
- DispatchServlet
配置之后,就可以在Servlet容器启动的时候,创建WebApplicationContext。从而启动Spring容器。下面介绍,如果通过硬编码的方法来启动做融合。
在这之前需要了解一下ServletContainerInitializer
和javax.servlet.annotation.HandlesTypes
Servlet提供的方式
1. ServletContainerInitializer和javax.servlet.annotation.HandlesTypes
- ServletContainerInitializer是在
javax.servlet
包下面的,它是一个接口,允许在WebApplication启动的阶段通知。此外这个接口一般都是要利用HandlesTypes
注解的。
1 | java复制代码package javax.servlet; |
- HandlesTypes注解
这注解是和ServletContainerInitializer一块使用的,它表示,value里面声明的类要传递给ServletContainerInitializer来做处理,value表示要探测的类。
1 | java复制代码@Target({ElementType.TYPE}) |
秘诀就是这个,对于Spring来说,就是继承这个类,在之前的时候做事情就好了。把之前在ContextLoaderListener
干的事情,一部分是可以放在这里来干的。
Spring利用ServletContainerInitializer来做处理
SpringServletContainerInitializer
可以看到,它实现了ServletContainerInitializer,并且在@HandlesTypes里写WebApplicationInitializer,这就会导致servlet容器会将所有的WebApplicationInitializer组装成一个set,作为参数传递进来。
可以看到,在这里面会实例化传递进来的WebApplicationInitializer(前提是,这个WebApplicationInitializer的实例不是接口,不是抽象类)
还会排序(只要实现了Order接口),然后就是循环调用WebApplicationInitializer了。
问题
- SpringServletContainerInitializer是怎么被加载的?
ServletContainerInitializer理论上来说,是一个SPI。既然是SPI,肯定是有一个写好的全路径的配置文件的。Spring-web是在META-INF/services/javax.servlet.ServletContainerInitializer里面。这才是一个典型的SPI加载的过程。突然想到了Dubbo。
2. SpringServletContainerInitializer和WebApplicationInitializer有啥区别?SpringServletContainerInitializer负责实例化WebApplicationInitializer。并且将ServletContext传递给WebApplicationInitializer。
WebApplicationInitializer是自定义ServletContext的。
1 | java复制代码@HandlesTypes(WebApplicationInitializer.class) |
WebApplicationInitializer
这个接口是通过编程的方式来配置ServletContext,和之前的web.xml的功能基本差不多,但这里是通过编程方式来做的。这个接口的实现类会自动被SpringServletContainerInitializer
被识别并且实例化。
话说回来,Spring能通过编程的方式来配置,本质上还是人家ServletContext
支持这种方式,要是没有对应的api,肯定是不行的。
这个方法本身也很简单,将servletContext传递过来,子类就可以自己做操作了
1 | java复制代码public interface WebApplicationInitializer { |
先看看类图
作为Spring来说,怎么可能就提供一个简简单单的接口呢?肯定是一堆实现类。下面具体看看他们的操作
AbstractContextLoaderInitializer
在这里面主要干了下面的几件事情
- 给ServletContext创建rootAppContext。
- 将创建好的rootAppContext传递过去,创建ContextLoaderListener。
- 给ContextLoaderListener设置ApplicationContextInitializer。
- 给servletContext添加listener。
ContextLoaderListener熟悉把。之前配置在web.xml中的listener。现在编码方式搞进去。并且它的这个构造方法也有有考究的。还记得这个吗?
在看看他的才有参数的构造函数,这都有是考究的。这里直接设置进去就不需要在ContextLoaderListener
创建rootWebApplicationContext。
并且这是抽象类,使用模板设计方法。将创建rootApplicationContext和获取ApplicationContextInitializer的操作留给子类。
1 | java复制代码public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer { |
AbstractDispatcherServletInitializer
看这个名字就知道了,给ServletContext配置了一个Servlet,(话说,Spring的起名字确实有一手)。
主要看了下面的几个事情
- 调用父类的
onStartup
方法。 - 开始给ServletContext注册DispatchServlet
1. 确定Servlet的名字(重写getServletName方法,可以改变servlet的名字)。
2. 创建webApplicationContext。
3. 创建DispatchServlet(createDispatcherServlet)。
4. 获取ApplicationContextInitializer(getServletApplicationContextInitializers)
5. 给DispatchServlet配置ApplicationContextInitializer。
6. 将servlet添加给ServletContext。
7. 设置刚刚添加进去的Servlet(通过ServletRegistration.Dynamic,默认支持servlet异步)
8. 获取Filter(getServletFilters)
9. 给Servlet添加Filter,并且解决重名问题。(利用循环解决重名问题,最多100),配置Filter
10. 留给子类拓展方法,主要是修改DispatchServlet在ServletContext中的属性,对比web.xml中`servlet`标签里面的配置(customizeRegistration)。
问题
1. 上面说的解决重名的问题,100是啥意思? 在调用servletContext添加filter的时候,如果重名,方法的返回值就是空,如果是空,就走到循环里面,下次的名字就是
1
2
3 > java复制代码registration = servletContext.addFilter(filterName + "#" + counter, filter);
>
>
counter会一直到100。如果到了100,就直接报错,同一个filter添加了100次。。也就是说,他允许添加一个filter的时候重名100个对象。 2. 重名的问题,在添加Servlet的时候会有吗? 会。如果重名就直接报错,他可没有处理的操作。
1 | java复制代码 |
AbstractAnnotationConfigDispatcherServletInitializer
这个类,更加的具体,创建具体的WebApplicationContext。重写了父类的createRootApplicationContext
和createServletApplicationContext
。提供了两个方法,指定配置类。(getRootConfigClasses,getServletConfigClasses)这样就可以利用配置类来启动了。
但是得注意,这里只是调用的context.register()
方法,要知道,这个方法之后是必要要调用refresh方法Spring容器才是可以继续启动的。但是这里没有哦。
1 | java复制代码public abstract class AbstractAnnotationConfigDispatcherServletInitializer |
okk。到这里编码方式实现Web.xml配置就结束了,可能会觉得突然断片了,其实后面的内容和从Servlet和Spring-web整合理解父子容器 ,是一样的逻辑。无非就是少了一步创建的过程。别的配置逻辑都是一样的。
上面的流程,结合从Servlet和Spring-web整合理解父子容器 ,画了一张图便于理解。
Springboot融合方式
有了上面的了解,Springboot就比较好理解了,因为Springboot有嵌入式的Server。所以Springboot的融合方式和上面的不一样。
按照上面的写法,干线分为两个部分
- 创建tomcat的时候创建
ServletContainerInitializer
做初始化rootwebApplication。 DispatchServlet
的webApplication初始化。
1. TomcatStarter
TomcatStarter实现了ServletContainerInitializer借口,这个接口的作用上面已经说了,但是需要注意这个类没有使用HandlesTypes
注解,所以,onStartup
方法里面第一个参数就是一个空参数。在这个里面主要是 触发ServletContextInitializer
.
下面看TomcatStarter是在哪里创建的,并且在Tomcat启动的时候ServletContextInitializer有用的类有哪些,都有什么作用?
1 | java复制代码class TomcatStarter implements ServletContainerInitializer { |
2. ServletContextInitializer在Springboot启动时候的作用
既然ServletContextInitializer是给Tomcat使用的,并且Springboot的Tomcat是嵌入式的,所以就从创建Tomcat的时候看,肯定有添加的操作。
ServletContextInitializer起作用的地方
ServletWebServerApplicationContext#onRefresh方法
这个方法是在Refresh方法里面定义的模板方法,留给子类拓展。ConfigurableApplicationContext接口中定义Refresh方法,并且在AbstractApplicationContext里面定义了refresh模板方法。
可以看到,在onRefresh调用了createWebServer
方法,创建了一个webServer。
1 | java复制代码 @Override |
创建webServer
简单的分析了一下,创建webServer不是这一篇文章的主题,之后专门出一篇,这里就简单的分析分析createWebServer代码逻辑,下面主要干了几点事情:
- 如果有ServletContext,直接调用
ServletContextInitializer
,这说明什么事情,Tomcat已经创建好了。 - 如果没有,先获取ServletWebServerFactory,调用
factory.getWebServer
方法,传递ServletContextInitializer
集合。获取webServer - 将webServer包装成
WebServerGracefulShutdownLifecycle,WebServerStartStopLifecycle
,并且将它注册到beanFactory中。
1 | java复制代码 private void createWebServer() { |
从上面有一个关键点(getSelfInitializer
方法),这方法返回值就是一个ServletContextInitializer
,那这就是我们的关注点。
有一个题外话,如果不清楚Spring的Refresh方法,有一个简单的方法,看日志看日志,然后搜,比如看下面这种日志:
从这个日志就可以知道,这里已经初始化好了webApplicationContext,所以,从这里看准没有错,然后就断点一点点的看。就ok了(一点点的小小的经验)
我们还是顺着上面的方法看下去,getSelfInitializer
已经传递给factory.getWebServer
方法了,下来的就简单了,看哪些方法传递了给他。
看看springboot支持哪些的嵌入式的服务器。
这里主要看TomcatServletWebServerFactory
TomcatServletWebServerFactory#getWebServer(ServletContextInitializer的地方)
在这里面会真正的创建webServer,这篇博客不是分析webServer的创建过程。这里主要看ServletContextInitializer
引用的地方。一直看,就会看到下面的代码
可以看到,这里添加了两个
1
java复制代码(servletContext) -> this.initParameters.forEach(servletContext::setInitParameter) // 设置init参数
1
java复制代码new SessionConfiguringInitializer(this.session) // 配置Session
1 | java复制代码 protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) { |
上面的方法返回的 ServletContextInitializer 数字,将会在TomcatServletWebServerFactory#configureContext里面,创建TomcatStarter
,将ServletContextInitializer数字作为参数传递过去。
总结:
到此,知道了在整个Springboot启动的时候,先是会有三个ServletContextInitializer发挥作用。他们是。在创建WebApplicationContext之后,还会获取别的ServletWebContextInitializer。来进行具体的配置化。
- ServletWebServerApplicationContext#selfInitialize方法,这就只有一个(这才是重点)
- AbstractServletWebServerFactory#mergeInitializers方法,这里面配置了两个(一个是配置session的,一个是配置init-param参数的)
给ServletContext创建WebApplication
上面已经说了,直接看selfInitialize方法,主要干了下面的几个事情
- 给ServletContext设置rootWebApplication,为当前的ApplicationContext。给当前的ApplicationContext设置ServletContext(prepareWebApplicationContext)
- 将ServletContext包装成ServletContextScope,注册到BeanFactory中,并且添加到servletContext的属性中。(ServletContextScope就是为了包装了ServletContext,为了方便的访问和获取值)
- 给bean工厂中注册servletContext(bean名字是ServletContext),ServletConfig(bean名字是servletConfig),parameterMap,这是ServletContext的参数,(这就是一个mapString:String,bean的名字是contextParameters),attributeMap,这是Servlet的属性,(这也是一个Map String:Object,bean的名字是contextAttributes)
- 遍历所有的ServletContextInitializer。调用onStartup方法将servletContext传递过去,来做定制化的配置。
1 | java复制代码private void selfInitialize(ServletContext servletContext) throws ServletException { |
到第四步的时候,已经给ServletContext创建好了WebApplicationContext了,重点是getServletContextInitializerBeans方法,主要的作用,
getServletContextInitializerBeans分析
ServletContextInitializerBeans继承了AbstractCollection,表示,他是一个集合。集合里面存放的是ServletContextInitializer。所以,上面的方法里面才可以利用forEarch来遍历。
不是说继承了AbstractCollection,什么不干,就可以利用forEarch遍历了,肯定还有操作,下面先看这个。
主要是重写了迭代器方法,迭代器返回的是sortedList的。sortedList是这里面用来存放数据的地方,在构造函数里面已经设置好了。所以就可以利用forearch了。
1 | java复制代码// 主要是重写了下面的方法 |
下面具体看看这里面干的事情
- 初始化initializers。
- 确认initializerTypes,默认是ServletContextInitializer。之后会通过这个类型会从beanFactory中获取对应的bean。(要知道从beanFactory中获取bean的操作会走一边获取bean的过程,自动注入也是在这里发生的)
- 根据initializerTypes的值去beanFactory中获取值,将这些bean添加到initializers(bean的集合)里面,并且会获取到这些特殊的bean处理的Resource,添加到seen里面。
- 利用适配器,对系统中还存在的Filter和Servlet做适配器。并且也会添加到initializers和seen
- 排序,赋值给sortedList。(想想之前的迭代器。就是利用这个来做处理的)
- 简单的打印日志
1 | java复制代码 public ServletContextInitializerBeans(ListableBeanFactory beanFactory, |
ServletContextInitializerBeans#addServletContextInitializerBeans
从这个方法里面会从beanFactory中获取值(要知道,从beanFactory中获取bean,要是没有的话,就直接会创建,并且在这个途中会应用到所有的BeanPostProcess,自动注入)
1 | java复制代码private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) { |
ServletContextInitializerBeans#addServletContextInitializerBean
上一步从beanFactory中获取到bean,然后通过不同的类型做处理。注意看addServletContextInitializerBean的source参数。source是ServletRegistrationBean里面的衍生物,下面的这几个,只是参数的类型不同,都是调用了addServletContextInitializerBean方法。将值添加到initializers和seen里面。
问题:
- 上面一直提到的seen和initializers是什么?
initializers是一个数组。
定义如下:
private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;
这里面保存的是key是class对象,value是这个class关联的ServletContextInitializer链表。到最后的时候,会把value的值组成一个list,赋值给sortedList
seen是一个Set
定义如下:
private final Set seen = new HashSet<>();
这个Set会在addServletContextInitializerBean里面添加值,这里面添加的值是ServletRegistrationBean或者FilterRegistrationBean或者DelegatingFilterProxyRegistrationBean或者ServletListenerRegistrationBean调用各自的方法获取的值,可以理解为这些的衍生物。所以是一个Set。
- FilterRegistrationBean和ServletRegistrationBean和DelegatingFilterProxyRegistrationBean和ServletListenerRegistrationBean都是一些什么东西
详情在下面看。
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 > > java复制代码 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和它的实现类分析
从上面的图可以很清楚的看到,他是实现了ServletContextInitializer接口,ServletContextInitializer。在创建WebApplicationContext的时候已经创建了三个了,这三个都交给TomcatStarter来循环调用了,所以,这里的这些实现类都是在容器里面注入的,和之前的是没有关系的,但是他们的功能都是一样,在ServletContext启动的时候配置东西。
既然实现了ServletContextInitializer接口,直接先看onStartup方法
1. RegistrationBean#onStartup方法
一个简单的模板方法,先获取description,在判断this是否启动,没有就直接打日志,有的话就调用register方法。
getDescription,isEnabled,register这都是留给子类来实现的。
1
2
3
4
5
6
7
8
9
10
11
12 > > java复制代码 @Override
> > public final void onStartup(ServletContext servletContext) throws ServletException {
> >
> > String description = getDescription();
> > if (!isEnabled()) {
> > logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
> > return;
> > }
> > register(description, servletContext);
> > }
> >
> >
继续按照上面的图来看,
2. DynamicRegistrationBean
在之前的基础上,增加了配置
Registration.Dynamic
的能力,Registration.Dynamic在之前介绍过,就是给ServletContext添加Servlet或者Filter之后的返回值,还记得上面说的 100吗?这里就在之前的基础上,增加了对配置到ServletContext中的Servlet和Filter做配置。
直接看它是怎么写
好家伙,写的还挺骚的,可以看到,处理继承RegistrationBean,还增加了范型支持,这里面处理的值是继承于Registration.Dynamic的。并且增加了一些基础的配置,比如asyncSupported,初始参数。
既然继承了RegistrationBean,直接看它重写的方法,register
在register里面又增加了模板方法。addRegistration和configure,先调用addRegistration将servletContext传递过去,作为子类来说,就可以做一些额外的配置了, 比如对于ServletRegistrationBean来说,就可以在这个里面添加DispatchServlet,在添加完了之后,ServletContext就可以返回一个Registration.Dynamic的子类,传递给configure,就可以做自定义的配置了,也可以重写这个方法,比如对于ServletRegistrationBean来说,配置init参数,添加Servlet能够处理的Mapping等扽信息。
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 > > java复制代码public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean {
> >
> > private static final Log logger = LogFactory.getLog(RegistrationBean.class);
> >
> > private String name;
> >
> > private boolean asyncSupported = true;
> >
> > private Map<String, String> initParameters = new LinkedHashMap<>();
> >
> > public void setName(String name) {
> > Assert.hasLength(name, "Name must not be empty");
> > this.name = name;
> > }
> >
> > public void setAsyncSupported(boolean asyncSupported) {
> > this.asyncSupported = asyncSupported;
> > }
> >
> > public boolean isAsyncSupported() {
> > return this.asyncSupported;
> > }
> >
> >
> > public void setInitParameters(Map<String, String> initParameters) {
> > Assert.notNull(initParameters, "InitParameters must not be null");
> > this.initParameters = new LinkedHashMap<>(initParameters);
> > }
> >
> > public Map<String, String> getInitParameters() {
> > return this.initParameters;
> > }
> >
> >
> > public void addInitParameter(String name, String value) {
> > Assert.notNull(name, "Name must not be null");
> > this.initParameters.put(name, value);
> > }
> >
> > @Override
> > protected final void register(String description, ServletContext servletContext) {
> > D registration = addRegistration(description, servletContext);
> > if (registration == null) {
> > logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
> > return;
> > }
> > configure(registration);
> > }
> >
> > protected abstract D addRegistration(String description, ServletContext servletContext);
> >
> > protected void configure(D registration) {
> > registration.setAsyncSupported(this.asyncSupported);
> > if (!this.initParameters.isEmpty()) {
> > registration.setInitParameters(this.initParameters);
> > }
> > }
> >
> >
> > protected final String getOrDeduceName(Object value) {
> > return (this.name != null) ? this.name : Conventions.getVariableName(value);
> > }
> > }
> >
> >
就先分析到这里把,子类的实现可以自己去看看,已经很清楚了,现在还有一个问题,这些DynamicRegistrationBean是在哪里注入的?
一开始我也不知道,说说我是怎么做的。比如DispatcherServletRegistrationBean吧。
先点开他,然后看它的引用,然后打new, 肯定是要创建的,基本上就搞定了。
这一看就是下面的两个,上面的是端点
在DispatcherServletAutoConfiguration里面,可以看到这个bean是需要一个dispatchServlet的,那这个DispatchServlet是在哪里注入的呢?
就在这个方法的上面,可以看到,直接new出了dispatchServlet,要清楚,这里的DisaptchServlet是添加到Bean容器里面的。所以,它也走bean的生命周期那一套。
addAdaptableBeans分析
显示从beanFactory中获取MultipartConfigElement,如果有多个,只获取一个。
重点就是下面的这几个方法了
1. addAsRegistrationBean方法 2. ServletRegistrationBeanAdapter类和FilterRegistrationBeanAdapter和ServletListenerRegistrationBeanAdapter
问题:
1. addAdaptableBeans的目的是为了什么? 我觉得是为了将applicationContext中的配置的Servlet和Filter添加到ServletContext中,并且容易配置。也就是说在Springboot中也是可以配置多个Servlet的。
1
2
3
4
5
6
7
8
9
10
11 > > java复制代码 protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
> > MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
> > addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
> > addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
> > for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
> > addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
> > new ServletListenerRegistrationBeanAdapter());
> > }
> > }
> >
> >
addAsRegistrationBean分析
从bean工厂中获取beanType类型的bean,这些bean没有在seen集合里面。得到一个list,遍历这list,调用传递进来的适配器,适配,给适配器设置原来bean的order,添加到seen和initializers里面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 > > java复制代码private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
> > Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
> > List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
> > for (Entry<String, B> entry : entries) {
> > String beanName = entry.getKey();
> > B bean = entry.getValue();
> > if (this.seen.add(bean)) {
> > // One that we haven't already seen
> > RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
> > int order = getOrder(bean);
> > registration.setOrder(order);
> > this.initializers.add(type, registration);
> > if (logger.isTraceEnabled()) {
> > logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
> > + order + ", resource=" + getResourceDescription(beanName, beanFactory));
> > }
> > }
> > }
> > }
> >
> >
下面的这三个都实现了RegistrationBeanAdapter接口,这个接口表示是可以将给定的bean转为RegistrationBean,这个RegistrationBean意味着在ServletContext中可以添加的类。重点看它的createRegistrationBean方法
ServletRegistrationBeanAdapter
就是将source转化为ServletRegistrationBean。要是再配置类里面注入的,可以手动的设置进去那个范型具体的值,但是这里就只能自己来了。
如果除了seen里面包含的bean之外,只有一个,url就是/,否则就是”/“ + name + “/“。
如果要创建的bean 的名字是DISPATCHER_SERVLET_NAME,就默认/,其余的就是创建ServletRegistrationBean,并且设置multipartConfig
1
2
3
4
5
6
7
8
9
10
11
12
13 > > java复制代码 @Override
> > public RegistrationBean createRegistrationBean(String name, Servlet source, int totalNumberOfSourceBeans) {
> > String url = (totalNumberOfSourceBeans != 1) ? "/" + name + "/" : "/";
> > if (name.equals(DISPATCHER_SERVLET_NAME)) {
> > url = "/"; // always map the main dispatcherServlet to "/"
> > }
> > ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(source, url);
> > bean.setName(name);
> > bean.setMultipartConfig(this.multipartConfig);
> > return bean;
> > }
> >
> >
FilterRegistrationBeanAdapter
这里其实和上面也是一样,不过返回的是FilterRegistrationBean。
1
2
3
4
5
6
7
8 > > java复制代码 @Override
> > public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) {
> > FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(source);
> > bean.setName(name);
> > return bean;
> > }
> >
> >
ServletListenerRegistrationBeanAdapter
这里也是一样的,返回的是ServletListenerRegistrationBean。
1
2
3
4
5
6
7 > > java复制代码 @Override
> > public RegistrationBean createRegistrationBean(String name, EventListener source,
> > int totalNumberOfSourceBeans) {
> > return new ServletListenerRegistrationBean<>(source);
> > }
> >
> >
到这里就知道了,ServletContextInitializerBeans里面干了什么事情。剩下的就是循环调用了。再结合上面的代码,总结下来就是:
1. 创建ServletContextInitializerBeans。再这里面会加载所有的实现了ServletContextInitializer的类, 2. 其中有一个特殊的bean(RegistrationBean),再这个实现类里面会配置ServletContext。 3. RegistrationBean的实现类是自动配置类注入的。其中就有DispatchServlet。会创建dispatchServlet,并且会创建DispatcherServletRegistrationBean,将DispatchServlet传回去,再调用ServletContextInitializer的onStartup方法的时候会将DispatchServlet添加到ServletContext中,并且做配置。
到现在已经说清楚了,webApplicationContext和ServletContext的关联,DispatchServlet和ServletContext的关联,但是现在还有一个问题,回想到之前说的,DispatchServlet也有一个webApplicationContext,一直到这里也没有看到。
下面就来分析分析
3. DispatchServlet中的applicationContext
再创建dispatchServlet的时候,webApplicationContext已经创建好。DispatchServlet已经添加容器了。也没有看到DispatchServlet设置applicationContext的操作。那它怎么是怎么做的?
dispatchServlet实现了ApplicationContextAware接口,通过这个接口就会将applicationContext传递过来,dispatchServlet创建的时候走了完整的过程,所以,这个applicationContext就是之前的Context。也就是和RootWebApplication一样。
举个例子看看
在之前的spring-web里面,有两个容器,子可以访问父,父不能访问子。也就是controller不能注入到service中,但是在Springboot中是可以的。
到此,从Servlet和SpringBoot整合理解父子容器就结束了。关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。
本文转载自: 掘金