从Servlet和SpringBoot整合理解父子容器

1,743 阅读19分钟

SpringBoot和Spring-web的容器分析

在这篇开始之前,建议先看从Servlet和Spring-web整合理解父子容器

编码方式实现Web.xml配置

通过上一篇的文章知道了,要实现Servlet和Spring-web融合,就需要在web.xml中主要配置两个东西

  1. ContextLoaderListener
  2. DispatchServlet

配置之后,就可以在Servlet容器启动的时候,创建WebApplicationContext。从而启动Spring容器。下面介绍,如果通过硬编码的方法来启动做融合。

在这之前需要了解一下ServletContainerInitializerjavax.servlet.annotation.HandlesTypes

Servlet提供的方式

1. ServletContainerInitializer和javax.servlet.annotation.HandlesTypes

  1. 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; 
    }
    
  2. 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了。

问题

  1. SpringServletContainerInitializer是怎么被加载的?

    ServletContainerInitializer理论上来说,是一个SPI。既然是SPI,肯定是有一个写好的全路径的配置文件的。Spring-web是在META-INF/services/javax.servlet.ServletContainerInitializer里面。这才是一个典型的SPI加载的过程。突然想到了Dubbo。

  2. 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) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				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;

}

先看看类图

图片.png

作为Spring来说,怎么可能就提供一个简简单单的接口呢?肯定是一堆实现类。下面具体看看他们的操作

AbstractContextLoaderInitializer

在这里面主要干了下面的几件事情

  1. 给ServletContext创建rootAppContext。
  2. 将创建好的rootAppContext传递过去,创建ContextLoaderListener。
  3. 给ContextLoaderListener设置ApplicationContextInitializer。
  4. 给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);
	}
   // 创建rootApplication,并且创建ContextLoaderListener,通过ApplicationContextInitializer来自定义,并且给servletContext添加listener
	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的起名字确实有一手)。

主要看了下面的几个事情

  1. 调用父类的onStartup方法。

  2. 开始给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的时候,如果重名,方法的返回值就是空,如果是空,就走到循环里面,下次的名字就是

      registration = servletContext.addFilter(filterName + "#" + counter, filter);
      

      counter会一直到100。如果到了100,就直接报错,同一个filter添加了100次。。也就是说,他允许添加一个filter的时候重名100个对象。

    2. 重名的问题,在添加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;
	}

	 // 给servlet创建ApplicationContext
	protected abstract WebApplicationContext createServletApplicationContext();

	// 创建dispatchServlet。
	protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
		return new DispatcherServlet(servletAppContext);
	}

	 // 获取ApplicationContextInitializer,这是专门给ServletApplicationContext中的
	@Nullable
	protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
		return null;
	}

	// 获取ServletMapping。这类比的就是<servlet-mapping>
	protected abstract String[] getServletMappings();

	
	@Nullable
	protected Filter[] getServletFilters() {
		return null;
	}
 // 注册filter
	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。重写了父类的createRootApplicationContextcreateServletApplicationContext。提供了两个方法,指定配置类。(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的融合方式和上面的不一样。

按照上面的写法,干线分为两个部分

  1. 创建tomcat的时候创建ServletContainerInitializer做初始化rootwebApplication。
  2. 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;
	}
   // 添加进来,tomcat启动得时候,就会调用,在Spring的编程配置web.xml的时候,会使用@TypeHandle的注解,但是这里不需要
	@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代码逻辑,下面主要干了几点事情:

  1. 如果有ServletContext,直接调用ServletContextInitializer,这说明什么事情,Tomcat已经创建好了。
  2. 如果没有,先获取ServletWebServerFactory,调用factory.getWebServer方法,传递ServletContextInitializer集合。获取webServer
  3. 将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方法,有一个简单的方法,看日志看日志,然后搜,比如看下面这种日志:

图片.png

从这个日志就可以知道,这里已经初始化好了webApplicationContext,所以,从这里看准没有错,然后就断点一点点的看。就ok了(一点点的小小的经验)


我们还是顺着上面的方法看下去,getSelfInitializer已经传递给factory.getWebServer方法了,下来的就简单了,看哪些方法传递了给他。

图片.png

看看springboot支持哪些的嵌入式的服务器。

这里主要看TomcatServletWebServerFactory

TomcatServletWebServerFactory#getWebServer(ServletContextInitializer的地方)

在这里面会真正的创建webServer,这篇博客不是分析webServer的创建过程。这里主要看ServletContextInitializer引用的地方。一直看,就会看到下面的代码

可以看到,这里添加了两个

  1. (servletContext) -> this.initParameters.forEach(servletContext::setInitParameter) // 设置init参数
    
  2. new SessionConfiguringInitializer(this.session) // 配置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。来进行具体的配置化。

  1. ServletWebServerApplicationContext#selfInitialize方法,这就只有一个(这才是重点)
  2. AbstractServletWebServerFactory#mergeInitializers方法,这里面配置了两个(一个是配置session的,一个是配置init-param参数的)

给ServletContext创建WebApplication

上面已经说了,直接看selfInitialize方法,主要干了下面的几个事情

  1. 给ServletContext设置rootWebApplication,为当前的ApplicationContext。给当前的ApplicationContext设置ServletContext(prepareWebApplicationContext)
  2. 将ServletContext包装成ServletContextScope,注册到BeanFactory中,并且添加到servletContext的属性中。(ServletContextScope就是为了包装了ServletContext,为了方便的访问和获取值)
  3. 给bean工厂中注册servletContext(bean名字是ServletContext),ServletConfig(bean名字是servletConfig),parameterMap,这是ServletContext的参数,(这就是一个mapString:String,bean的名字是contextParameters),attributeMap,这是Servlet的属性,(这也是一个Map String:Object,bean的名字是contextAttributes)
  4. 遍历所有的ServletContextInitializer。调用onStartup方法将servletContext传递过去,来做定制化的配置。
private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareWebApplicationContext(servletContext); //给当前的servletCOntext设置rootWebApplication,将当前的applicationContext设置,并且也设置ServletContext
		registerApplicationScope(servletContext); // 这个简单了,创建一个ServletContextScope,然后讲他添加到servletContext和applicationContext中
		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
		// 这里会创建dispatchServlet了 lcnote 这里得注意
		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();
	}

下面具体看看这里面干的事情

  1. 初始化initializers。
  2. 确认initializerTypes,默认是ServletContextInitializer。之后会通过这个类型会从beanFactory中获取对应的bean。(要知道从beanFactory中获取bean的操作会走一边获取bean的过程,自动注入也是在这里发生的)
  3. 根据initializerTypes的值去beanFactory中获取值,将这些bean添加到initializers(bean的集合)里面,并且会获取到这些特殊的bean处理的Resource,添加到seen里面。
  4. 利用适配器,对系统中还存在的Filter和Servlet做适配器。并且也会添加到initializers和seen
  5. 排序,赋值给sortedList。(想想之前的迭代器。就是利用这个来做处理的)
  6. 简单的打印日志
	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里面。

问题:

  1. 上面一直提到的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。

  2. FilterRegistrationBean和ServletRegistrationBean和DelegatingFilterProxyRegistrationBean和ServletListenerRegistrationBean都是一些什么东西

    详情在下面看。

  3. 	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和它的实现类分析

    图片.png

    从上面的图可以很清楚的看到,他是实现了ServletContextInitializer接口,ServletContextInitializer。在创建WebApplicationContext的时候已经创建了三个了,这三个都交给TomcatStarter来循环调用了,所以,这里的这些实现类都是在容器里面注入的,和之前的是没有关系的,但是他们的功能都是一样,在ServletContext启动的时候配置东西。

    既然实现了ServletContextInitializer接口,直接先看onStartup方法

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

    继续按照上面的图来看,

    1. 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, 肯定是要创建的,基本上就搞定了。

    图片.png

    这一看就是下面的两个,上面的是端点

    图片.png

    在DispatcherServletAutoConfiguration里面,可以看到这个bean是需要一个dispatchServlet的,那这个DispatchServlet是在哪里注入的呢?

    图片.png

    就在这个方法的上面,可以看到,直接new出了dispatchServlet,要清楚,这里的DisaptchServlet是添加到Bean容器里面的。所以,它也走bean的生命周期那一套。

    addAdaptableBeans分析

    显示从beanFactory中获取MultipartConfigElement,如果有多个,只获取一个。

    重点就是下面的这几个方法了

    1. addAsRegistrationBean方法
    2. ServletRegistrationBeanAdapter类和FilterRegistrationBeanAdapter和ServletListenerRegistrationBeanAdapter

    问题:

    1. 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)) {
    				// 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

    	@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。

    	@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里面干了什么事情。剩下的就是循环调用了。再结合上面的代码,总结下来就是:

    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的操作。那它怎么是怎么做的?

    图片.png

    dispatchServlet实现了ApplicationContextAware接口,通过这个接口就会将applicationContext传递过来,dispatchServlet创建的时候走了完整的过程,所以,这个applicationContext就是之前的Context。也就是和RootWebApplication一样。

    举个例子看看

    在之前的spring-web里面,有两个容器,子可以访问父,父不能访问子。也就是controller不能注入到service中,但是在Springboot中是可以的。

    图片.png 到此,从Servlet和SpringBoot整合理解父子容器就结束了。

    关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。