在使用Spring MVC时候大部分同窗都会定义两个配置文件,一个是Spring的配置文件spring.xml,另外一个是Spring MVC的配置文件spring-mvc.xml。html
在这里给你们抛个问题,若是在spring.xml和spring-mvc.xml文件中同时定义一个相同id的单例bean会怎样呢?你们能够先思考一下再继续往下看。java
我作了个实验,结论是:容器中会同时存在两个相同id 的bean,并且使用起来互不干扰。web
这是为何呢?学过Spring的同窗确定会质疑,众所周知id是bean的惟一标示,怎么可能同时存在两个相同id的bean呢?是否是我在胡扯呢?spring
原谅我在这和你们卖了个关子,其实你们说的都没错,由于这里涉及到Spring MVC父子容器的知识点。api
这个知识点是:在使用Spring MVC过程当中会存在Spring MVC 、Spring两个IOC容器,且Spring MVC是Spring的子容器。spring-mvc
那这个父子容器究竟是什么呢?bash
为了保证我所说的权威性,而不是知识的二道贩子,我将从Spring 官方文档和源码两方面展开介绍。mvc
仍是先找程序入口,查看web.xml配置文件,找到Spring MVC相关配置。app
<servlet>
<servlet-name>spring-mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
复制代码
配置很简单,只是配置了一个类型为DispatcherServlet类型的Servlet,并设置了初始化参数。那DispatcherServlet是什么呢?ide
查看API文档
在整个过程当中DispatcherServlet承当了一个中心控制器的角色来处理各类请求。
上图来自Spring官网:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html
复制代码
从图中能够看到DispatcherServlet里面有一个 Servlet WebApplicationContext,继承自 Root WebApplicationContext。
从上篇文章中咱们知道WebApplicationContext其实就是一个IOC容器,root WebApplicationContext是Spring容器。
这说明DispatcherServlet中里建立了一个IOC容器而且这个容器继承了Spring 容器,也就是Spring的子容器。
并且官方文档中还有以下一段文字描述:
For many applications, having a single WebApplicationContext is simple and suffices. It is also possible to have a context hierarchy where one root WebApplicationContext is shared across multiple DispatcherServlet (or other Servlet) instances, each with its own child WebApplicationContext configuration. See Additional Capabilities of the ApplicationContext for more on the context hierarchy feature.
The root WebApplicationContext typically contains infrastructure beans, such as data repositories and business services that need to be shared across multiple Servlet instances.
Those beans are effectively inherited and can be overridden (that is, re-declared) in the Servlet-specific child WebApplicationContext, which typically contains beans local to the given Servlet.
复制代码
结合图和上述文字咱们能够得出如下信息:
- 应用中能够包含多个IOC容器。
- DispatcherServlet的建立的子容器主要包含Controller、view resolvers等和web相关的一些bean。
- 父容器root WebApplicationContex主要包含包含一些基础的bean,好比一些须要在多个servlet共享的dao、service等bean。
- 若是在子容器中找不到bean的时候能够去父容器查找bean。
看到这里也许你们心中也许就明白文章开头中我说的Spring MVC中的父子容器了,对那个问题也有了本身的判断和答案。
固然文章尚未结束,毕竟这还仅限于对官方文档的理解,为了进一步验证,咱们拿出终极武器:
阅读源码!
本小节咱们分为Spring MVC容器的建立和bean的获取两部分进行分析。
前面分析到DispatcherServlet本质上仍是一个Servlet ,既然是Servlet ,了解Servlet生命周期的同窗都知道Web 容器装载Servlet第一步是执行init()函数,所以以DispatcherServlet 的init函数为突破口进行分析。
@Override
public final void init() throws ServletException {
// 1.读取init parameters 等参数,其中就包括设置contextConfigLocation
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//2.初始化servlet中使用的bean
initServletBean();
}
复制代码
在第1步读取init parameter的函数最终会调用setContextConfigLocation()设置配置文件路径。此处重点介绍initServletBean(),继续跟踪。
Override
protected final void initServletBean() throws ServletException {
//初始化webApplicationContext
this.webApplicationContext = initWebApplicationContext();
}
复制代码
protected WebApplicationContext initWebApplicationContext() {
//1.得到rootWebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//2.若是尚未webApplicatioinContext,建立webApplicationContext
if (wac == null) {
//建立webApplicationContext
wac = createWebApplicationContext(rootContext);
}
return wac;
}
复制代码
能够看到上面初始化webApplicationContext分为2步。
咱们先看看rootWebApplicationContext是如何获取的。
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Object attr = sc.getAttribute(attrName);
return (WebApplicationContext) attr;
}
复制代码
从上面代码中我没看到是从ServletContext获取了名为“WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE”的webApplicationContext。
认真看过上篇文章的同窗应该记得这个属性是在Spring初始化 容器initWebApplicationContext()函数中的第3步设置进去的,取得的值即Spring IOC容器。
继续看如何建立webApplicationContext。
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
复制代码
createWebApplicationContext(ApplicationContext parent) {
//1.获取WebApplicationContext实现类,此处其实就是XmlWebApplicationContext
Class<?> contextClass = getContextClass();
//生成XmlWebApplicationContext实例
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//2.设置rootWebApplicationContext为父容器
wac.setParent(parent);
//3.设置配置文件
wac.setConfigLocation(getContextConfigLocation());
//4.配置webApplicationContext.
configureAndRefreshWebApplicationContext(wac);
return wac;
}
复制代码
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//开始处理bean
wac.refresh();
}
复制代码
看到这里同窗们有没有是曾相识的感受。是的,这段逻辑和上篇文章建立Spring IOC的逻辑相似。
惟一不一样的是在第2步会把Spring容器设置为本身的父容器。至于新建容器中bean的注册、解析、实例化等流程和Spring IOC容器同样都是交给XmlWebApplicationContext类处理,尚未掌握的同窗能够看上篇文章。
Spring MVC bean的获取其实咱们在上篇文章已经介绍过,此次再单拎出来介绍一下,加深记忆。
protected <T> T doGetBean(
// 获取父BeanFactory
BeanFactory parentBeanFactory = getParentBeanFactory();
//若是父容器不为空,且本容器没有注册此bean就去父容器中获取bean
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// 若是父容器有该bean,则调用父beanFactory的方法得到该bean
return (T) parentBeanFactory.getBean(nameToLookup,args);
}
//若是子容器注册了bean,执行一系列实例化bean操做后返回bean.
//此处省略实例化过程
.....
return (T) bean;
}
复制代码
上面代码就能够对应官方文档中“若是子容器中找不到bean,就去父容器找”的解释了。
看完上面的介绍,相信你们对Spring MVC父子容器的概念都有所了解,如今咱们分析文章开头的问题。
若是spring.xml和spring-mvc.xml定义了相同id的bean会怎样?假设id=test。
1.首先Spring 初始化,Spring IOC 容器中生成一个id为test bean实例。
2.Spring MVC开始初始化,生成一个id为test bean实例。
此时,两个容器分别有一个相同id的bean。那用起来会不会混淆?
答案是不会。
当你在Spring MVC业务逻辑中使用该bean时,Spring MVC会直接返回本身容器的bean。
当你在Spring业务逻辑中使用该bean时,由于子容器的bean对父亲是不可见的,所以会直接返回Spring容器中的bean。
虽然上面的写法不会形成问题。可是在实际使用过程当中,建议你们都把bean定义都写在spring.xml文件中。
由于使用单例bean的初衷是在IOC容器中只存在一个实例,若是两个配置文件都定义,会产生两个相同的实例,形成资源的浪费,也容易在某些场景下引起缺陷。
如今你们基本都不使用在xml文件中定义bean的形式,而是用注解来定义bean,而后在xml文件中定义扫描包。以下:
<context:component-scan base-package="xx.xx.xx"/>
复制代码
那若是在spring.xml和spring-mvc.xml配置了重复的包会怎样呢?
若是本文看明白的同窗此时已经知道了答案。
答案是会在两个父子IOC容器中生成大量的相同bean,这就会形成内存资源的浪费。
也许有同窗想到,那只在spring.xml中设置扫描包不就能避免这种问题发生了吗,答案是这样吗?
你们能够试试,这样会有什么问题。若是不行,那是为何呢?
欲知分晓,敬请期待下篇分解!
想要了解更多,关注公众号:七分熟pizza