IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 从Servlet和SpringBoot整合理解父子容器 -> 正文阅读

[Java知识库]从Servlet和SpringBoot整合理解父子容器

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;

}

先看看类图

在这里插入图片描述

作为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方法,有一个简单的方法,看日志看日志,然后搜,比如看下面这种日志:

在这里插入图片描述

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


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

在这里插入图片描述

看看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都是一些什么东西

    详情在下面看。

	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这都是留给子类来实现的。

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

举个例子看看

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

在这里插入图片描述到此,从Servlet和SpringBoot整合理解父子容器就结束了。

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-11-19 17:30:07  更:2021-11-19 17:30:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:47:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码