父子容器概念
父子容器并非 Spring MVC 的专利,在普通的 Spring 环境下 Spring 就已经设计出具有层次结构的容器了,这种设计方式也并非 Spring 独创,其工作方式和 ClassLoader (类加载器)很相似,每个容器有一个自己的父容器,但是与 ClassLoader 不同的是,通过容器查找 bean 时是优先从子容器查找,如果找不到才会从父容器中查找。当应用中存在多个容器时,这种设计方式可以将公共的 bean 放到父容器中,如果父容器中的 bean 不适用,子容器还可以覆盖父容器中的 bean。
根容器(父容器)通常包含基础设施bean,例如需要在多个Servlet实例之间共享的数据存储库和业务服务。 这些bean被有效地继承,并且可以在特定于Servlet的子容器中被覆盖(即重新声明),该子容器通常包含给定Servlet的本地bean。 下图显示了这种关系:
代码实现:
需要继承AbstractAnnotationConfigDispatcherServletInitializer类,并实现三个抽象方法:
public class QuickWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override //spring配置文件
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{AppConfig.class};
}
@Override //springmvc的配置文件
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{MvcConfig.class};
}
@Override //servlet映射
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
两个配置类:
@ComponentScan("com.wj.service")
@Configuration
public class AppConfig {
}
@Configuration
@ComponentScan("com.wj.controller")
public class MvcConfig {
}
项目结构:这里MyWebApplicationInitializer不用管。
按照上述配置后,就创建了一个父子容器。
那么如何验证我们创建了父子容器,我们在controller注入applicationContext,然后看applicationContext的parent字段,如果创建成功,则parent字段中一定有父容器:
创建父子容器原理
上面创建父子容器案例中,我们继承AbstractAnnotationConfigDispatcherServletInitializer
类后,就自动创建了父子容器。
我们先来看类的继承关系:
该类实现了WebApplicationInitializer
,那么势必会调用onStartup
方法(具体原理可看前文:https://www.cnblogs.com/wwjj4811/p/15673030.html)
先来看AbstractContextLoaderInitializer
的onStartup方法:
这里调用registerContextLoaderListener
方法,内部会增加一个ContextLoaderListener
的监听器。
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//注册ContextLoaderListener
registerContextLoaderListener(servletContext);
}
/**
* Register a {@link ContextLoaderListener} against the given servlet context. The
* {@code ContextLoaderListener} is initialized with the application context returned
* from the {@link #createRootApplicationContext()} template method.
* @param servletContext the servlet context to register the listener against
*/
protected void registerContextLoaderListener(ServletContext servletContext) {
//创建一个根容器
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
//增加一个ContextLoaderListener
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
//添加ApplicationContextInitializer
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
//创建根容器代码在AbstractAnnotationConfigDispatcherServletInitializer中
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
//获取根配置类(留给子类实现)
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
//创建IOC容器
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
//注册配置类
context.register(configClasses);
return context;
}
else {
return null;
}
}
而ContextLoaderListener
实际上是一个ServletContextListener
。
当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该事件由 ServletContextListener 来处理。在 ServletContextListener 接口中定义了处理ServletContextEvent 事件的两个方法。
contextInitialized(ServletContextEvent sce) :当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。
contextDestroyed(ServletContextEvent sce) :当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。
再看ContextLoaderListener
的contextInitialized方法,看看它在servlet启动时候做了什么事情?
@Override
public void contextInitialized(ServletContextEvent event) {
//初始化根容器
initWebApplicationContext(event.getServletContext());
}
initWebApplicationContext
方法如下:这里是初始化父容器
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
//这里cwac.getParent() 默认为null
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
//loadParentContext()方法默认返回的也是null
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//配置和刷新web容器,这里不再介绍,详情可看https://www.cnblogs.com/wwjj4811/p/15673030.html
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//向servletContext设置根容器
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
所以说父容器要比子容器先初始化。
那么,DispatcherServlet在哪里创建的呢?
这就要看AbstractDispatcherServletInitializer
类:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//调用父类AbstractContextLoaderInitializer的onStartup
//注册DispatcherServlet
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
/**
* Register a {@link DispatcherServlet} against the given servlet context.
* <p>This method will create a {@code DispatcherServlet} with the name returned by
* {@link #getServletName()}, initializing it with the application context returned
* from {@link #createServletApplicationContext()}, and mapping it to the patterns
* returned from {@link #getServletMappings()}.
* <p>Further customization can be achieved by overriding {@link
* #customizeRegistration(ServletRegistration.Dynamic)} or
* {@link #createDispatcherServlet(WebApplicationContext)}.
* @param servletContext the context to register the servlet against
*/
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
//创建Servlet容器(子容器)
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
//创建DispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//增加servlet
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);
}
}
//留给子类实现的方法:用于自定义ServletRegistration
customizeRegistration(registration);
}
这里创建了WebApplicationContext子容器,然后创建DispatcherServlet。创建了DispatcherServlet后,后面在DispatcherServlet初始化时候,会调用它的init()方法,又会走到FrameworkServlet
类的initWebApplicationContext
方法:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());//获取父容器
WebApplicationContext wac = null;//子容器
//this.webApplicationContext 是之前createServletApplicationContext()创建的Servlet容器
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;//当前web的IOC容器
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);//设置父子容器关系
}
//配置和刷新容器
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
//onRefresh方法模板,DispatcherServlet实现了改方法
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
至此父子容器关系建立,并且父容器和子容器刷新配置完成。
这就是SpringMVC父子容器创建的原理。
父子容器特点
- 父容器和子容器是相互隔离的,他们内部可以存在名称相同的bean
- 子容器可以访问父容器中的bean,而父容器不能访问子容器中的bean
- 调用子容器的getBean方法获取bean的时候,会沿着当前容器开始向上面的容器进行查找,直到找到对应的bean为止
- 子容器中可以通过任何注入方式注入父容器中的bean,而父容器中是无法注入子容器中的bean,原因是第2点