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 注解的。 package javax.servlet;
import java.util.Set;
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
-
HandlesTypes注解 这注解是和ServletContainerInitializer一块使用的,它表示,value里面声明的类要传递给ServletContainerInitializer来做处理,value表示要探测的类。 @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
Class<?>[] value();
}
秘诀就是这个,对于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。 -
SpringServletContainerInitializer和WebApplicationInitializer有啥区别? SpringServletContainerInitializer负责实例化WebApplicationInitializer。并且将ServletContext传递给WebApplicationInitializer。 WebApplicationInitializer是自定义ServletContext的。
@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) {
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) {
initializer.onStartup(servletContext);
}
}
}
WebApplicationInitializer
这个接口是通过编程的方式来配置ServletContext,和之前的web.xml的功能基本差不多,但这里是通过编程方式来做的。这个接口的实现类会自动被SpringServletContainerInitializer 被识别并且实例化。
话说回来,Spring能通过编程的方式来配置,本质上还是人家ServletContext 支持这种方式,要是没有对应的api,肯定是不行的。
这个方法本身也很简单,将servletContext传递过来,子类就可以自己做操作了
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
先看看类图
作为Spring来说,怎么可能就提供一个简简单单的接口呢?肯定是一堆实现类。下面具体看看他们的操作
AbstractContextLoaderInitializer
在这里面主要干了下面的几件事情
- 给ServletContext创建rootAppContext。
- 将创建好的rootAppContext传递过去,创建ContextLoaderListener。
- 给ContextLoaderListener设置ApplicationContextInitializer。
- 给servletContext添加listener。
ContextLoaderListener熟悉把。之前配置在web.xml中的listener。现在编码方式搞进去。并且它的这个构造方法也有有考究的。还记得这个吗?
在看看他的才有参数的构造函数,这都有是考究的。这里直接设置进去就不需要在ContextLoaderListener 创建rootWebApplicationContext。
并且这是抽象类,使用模板设计方法。将创建rootApplicationContext和获取ApplicationContextInitializer的操作留给子类。
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
protected final Log logger = LogFactory.getLog(getClass());
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
@Nullable
protected abstract WebApplicationContext createRootApplicationContext();
@Nullable
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
AbstractDispatcherServletInitializer
看这个名字就知道了,给ServletContext配置了一个Servlet,(话说,Spring的起名字确实有一手)。
主要看了下面的几个事情
-
调用父类的onStartup 方法。 -
开始给ServletContext注册DispatchServlet
- 确定Servlet的名字(重写getServletName方法,可以改变servlet的名字)。
- 创建webApplicationContext。
- 创建DispatchServlet(createDispatcherServlet)。
- 获取ApplicationContextInitializer(getServletApplicationContextInitializers)
- 给DispatchServlet配置ApplicationContextInitializer。
- 将servlet添加给ServletContext。
- 设置刚刚添加进去的Servlet(通过ServletRegistration.Dynamic,默认支持servlet异步)
- 获取Filter(getServletFilters)
- 给Servlet添加Filter,并且解决重名问题。(利用循环解决重名问题,最多100),配置Filter
- 留给子类拓展方法,主要是修改DispatchServlet在ServletContext中的属性,对比web.xml中
servlet 标签里面的配置(customizeRegistration)。
问题
-
上面说的解决重名的问题,100是啥意思? 在调用servletContext添加filter的时候,如果重名,方法的返回值就是空,如果是空,就走到循环里面,下次的名字就是 registration = servletContext.addFilter(filterName + "#" + counter, filter);
counter会一直到100。如果到了100,就直接报错,同一个filter添加了100次。。也就是说,他允许添加一个filter的时候重名100个对象。 -
重名的问题,在添加Servlet的时候会有吗? 会。如果重名就直接报错,他可没有处理的操作。
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
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());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
protected String getServletName() {
return DEFAULT_SERVLET_NAME;
}
protected abstract WebApplicationContext createServletApplicationContext();
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
@Nullable
protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return null;
}
protected abstract String[] getServletMappings();
@Nullable
protected Filter[] getServletFilters() {
return null;
}
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
String filterName = Conventions.getVariableName(filter);
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
int counter = 0;
while (registration == null) {
if (counter == 100) {
throw new IllegalStateException("Failed to register filter with name '" + filterName + "'. " +
"Check if there is another filter registered under the same name.");
}
registration = servletContext.addFilter(filterName + "#" + counter, filter);
counter++;
}
}
registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}
protected boolean isAsyncSupported() {
return true;
}
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
}
}
AbstractAnnotationConfigDispatcherServletInitializer
这个类,更加的具体,创建具体的WebApplicationContext。重写了父类的createRootApplicationContext 和createServletApplicationContext 。提供了两个方法,指定配置类。(getRootConfigClasses,getServletConfigClasses)这样就可以利用配置类来启动了。
但是得注意,这里只是调用的context.register() 方法,要知道,这个方法之后是必要要调用refresh方法Spring容器才是可以继续启动的。但是这里没有哦。
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null;
}
}
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
@Nullable
protected abstract Class<?>[] getRootConfigClasses();
@Nullable
protected abstract Class<?>[] getServletConfigClasses();
}
okk。到这里编码方式实现Web.xml配置就结束了,可能会觉得突然断片了,其实后面的内容和从Servlet和Spring-web整合理解父子容器 ,是一样的逻辑。无非就是少了一步创建的过程。别的配置逻辑都是一样的。
上面的流程,结合从Servlet和Spring-web整合理解父子容器 ,画了一张图便于理解。
【Tomacat和Spring启动的过程图示】
Springboot融合方式
有了上面的了解,Springboot就比较好理解了,因为Springboot有嵌入式的Server。所以Springboot的融合方式和上面的不一样。
按照上面的写法,干线分为两个部分
- 创建tomcat的时候创建
ServletContainerInitializer 做初始化rootwebApplication。 DispatchServlet 的webApplication初始化。
1. TomcatStarter
TomcatStarter实现了ServletContainerInitializer借口,这个接口的作用上面已经说了,但是需要注意这个类没有使用HandlesTypes 注解,所以,onStartup 方法里面第一个参数就是一个空参数。在这个里面主要是 触发ServletContextInitializer .
下面看TomcatStarter是在哪里创建的,并且在Tomcat启动的时候ServletContextInitializer有用的类有哪些,都有什么作用?
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;
/
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
+ ex.getMessage());
}
}
}
Exception getStartUpException() {
return this.startUpException;
}
}
2. ServletContextInitializer在Springboot启动时候的作用
既然ServletContextInitializer是给Tomcat使用的,并且Springboot的Tomcat是嵌入式的,所以就从创建Tomcat的时候看,肯定有添加的操作。
ServletContextInitializer起作用的地方
ServletWebServerApplicationContext#onRefresh方法
这个方法是在Refresh方法里面定义的模板方法,留给子类拓展。ConfigurableApplicationContext接口中定义Refresh方法,并且在AbstractApplicationContext里面定义了refresh模板方法。
可以看到,在onRefresh调用了createWebServer 方法,创建了一个webServer。
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
创建webServer
简单的分析了一下,创建webServer不是这一篇文章的主题,之后专门出一篇,这里就简单的分析分析createWebServer代码逻辑,下面主要干了几点事情:
- 如果有ServletContext,直接调用
ServletContextInitializer ,这说明什么事情,Tomcat已经创建好了。 - 如果没有,先获取ServletWebServerFactory,调用
factory.getWebServer 方法,传递ServletContextInitializer 集合。获取webServer - 将webServer包装成
WebServerGracefulShutdownLifecycle,WebServerStartStopLifecycle ,并且将它注册到beanFactory中。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
从上面有一个关键点(getSelfInitializer 方法),这方法返回值就是一个ServletContextInitializer ,那这就是我们的关注点。
有一个题外话,如果不清楚Spring的Refresh方法,有一个简单的方法,看日志看日志,然后搜,比如看下面这种日志:
从这个日志就可以知道,这里已经初始化好了webApplicationContext,所以,从这里看准没有错,然后就断点一点点的看。就ok了(一点点的小小的经验)
我们还是顺着上面的方法看下去,getSelfInitializer 已经传递给factory.getWebServer 方法了,下来的就简单了,看哪些方法传递了给他。
看看springboot支持哪些的嵌入式的服务器。
这里主要看TomcatServletWebServerFactory
TomcatServletWebServerFactory#getWebServer(ServletContextInitializer的地方)
在这里面会真正的创建webServer,这篇博客不是分析webServer的创建过程。这里主要看ServletContextInitializer 引用的地方。一直看,就会看到下面的代码
可以看到,这里添加了两个
-
(servletContext) -> this.initParameters.forEach(servletContext::setInitParameter)
-
new SessionConfiguringInitializer(this.session)
protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) {
List<ServletContextInitializer> mergedInitializers = new ArrayList<>(); ServletContextInitializer的实现类,
mergedInitializers.add((servletContext) -> this.initParameters.forEach(servletContext::setInitParameter));
mergedInitializers.add(new SessionConfiguringInitializer(this.session));
mergedInitializers.addAll(Arrays.asList(initializers));
mergedInitializers.addAll(this.initializers);
return mergedInitializers.toArray(new ServletContextInitializer[0]);
}
上面的方法返回的 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传递过去,来做定制化的配置。
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
到第四步的时候,已经给ServletContext创建好了WebApplicationContext了,重点是getServletContextInitializerBeans方法,主要的作用,
getServletContextInitializerBeans分析
ServletContextInitializerBeans继承了AbstractCollection,表示,他是一个集合。集合里面存放的是ServletContextInitializer。所以,上面的方法里面才可以利用forEarch来遍历。
不是说继承了AbstractCollection,什么不干,就可以利用forEarch遍历了,肯定还有操作,下面先看这个。
主要是重写了迭代器方法,迭代器返回的是sortedList的。sortedList是这里面用来存放数据的地方,在构造函数里面已经设置好了。所以就可以利用forearch了。
@Override
public Iterator<ServletContextInitializer> iterator() {
return this.sortedList.iterator();
}
@Override
public int size() {
return this.sortedList.size();
}
下面具体看看这里面干的事情
- 初始化initializers。
- 确认initializerTypes,默认是ServletContextInitializer。之后会通过这个类型会从beanFactory中获取对应的bean。(要知道从beanFactory中获取bean的操作会走一边获取bean的过程,自动注入也是在这里发生的)
- 根据initializerTypes的值去beanFactory中获取值,将这些bean添加到initializers(bean的集合)里面,并且会获取到这些特殊的bean处理的Resource,添加到seen里面。
- 利用适配器,对系统中还存在的Filter和Servlet做适配器。并且也会添加到initializers和seen
- 排序,赋值给sortedList。(想想之前的迭代器。就是利用这个来做处理的)
- 简单的打印日志
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializers = new LinkedMultiValueMap<>();
this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
: Collections.singletonList(ServletContextInitializer.class);
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);
logMappings(this.initializers);
}
ServletContextInitializerBeans#addServletContextInitializerBeans
从这个方法里面会从beanFactory中获取值(要知道,从beanFactory中获取bean,要是没有的话,就直接会创建,并且在这个途中会应用到所有的BeanPostProcess,自动注入)
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
initializerType)) {
addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), 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都是一些什么东西 详情在下面看。
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方法
- RegistrationBean#onStartup方法
一个简单的模板方法,先获取description,在判断this是否启动,没有就直接打日志,有的话就调用register方法。
getDescription,isEnabled,register这都是留给子类来实现的。
@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);
}
继续按照上面的图来看,
- 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等扽信息。
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,如果有多个,只获取一个。
重点就是下面的这几个方法了
- addAsRegistrationBean方法
- ServletRegistrationBeanAdapter类和FilterRegistrationBeanAdapter和ServletListenerRegistrationBeanAdapter
问题:
- addAdaptableBeans的目的是为了什么?
我觉得是为了将applicationContext中的配置的Servlet和Filter添加到ServletContext中,并且容易配置。也就是说在Springboot中也是可以配置多个Servlet的。
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里面。
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)) {
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
@Override
public RegistrationBean createRegistrationBean(String name, Servlet source, int totalNumberOfSourceBeans) {
String url = (totalNumberOfSourceBeans != 1) ? "/" + name + "/" : "/";
if (name.equals(DISPATCHER_SERVLET_NAME)) {
url = "/";
}
ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(source, url);
bean.setName(name);
bean.setMultipartConfig(this.multipartConfig);
return bean;
}
FilterRegistrationBeanAdapter
这里其实和上面也是一样,不过返回的是FilterRegistrationBean。
@Override
public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(source);
bean.setName(name);
return bean;
}
ServletListenerRegistrationBeanAdapter
这里也是一样的,返回的是ServletListenerRegistrationBean。
@Override
public RegistrationBean createRegistrationBean(String name, EventListener source,
int totalNumberOfSourceBeans) {
return new ServletListenerRegistrationBean<>(source);
}
到这里就知道了,ServletContextInitializerBeans里面干了什么事情。剩下的就是循环调用了。再结合上面的代码,总结下来就是:
-
创建ServletContextInitializerBeans。再这里面会加载所有的实现了ServletContextInitializer的类, -
其中有一个特殊的bean(RegistrationBean),再这个实现类里面会配置ServletContext。 -
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整合理解父子容器就结束了。
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。
|