在使用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文檔
從繼承圖看出最終繼承自HttpServlet,其實就是一個普通的Servlet。那爲何這個Servlet就能完成Spring MVC一系列複雜的功能呢?繼續往下看。在整個過程當中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