javascript
boot spring 没有父子容器_Spring 系列(二):Spring MVC的父子容器
1.背景
在使用Spring MVC時(shí)候大部分同學(xué)都會(huì)定義兩個(gè)配置文件,一個(gè)是Spring的配置文件spring.xml,另一個(gè)是Spring MVC的配置文件spring-mvc.xml。
在這里給大家拋個(gè)問題,如果在spring.xml和spring-mvc.xml文件中同時(shí)定義一個(gè)相同id的單例bean會(huì)怎樣呢?大家可以先思考一下再繼續(xù)往下看。
我做了個(gè)實(shí)驗(yàn),結(jié)論是:容器中會(huì)同時(shí)存在兩個(gè)相同id 的bean,而且使用起來互不干擾。
這是為什么呢?學(xué)過Spring的同學(xué)肯定會(huì)質(zhì)疑,眾所周知id是bean的唯一標(biāo)示,怎么可能同時(shí)存在兩個(gè)相同id的bean呢?是不是我在胡扯呢?
原諒我在這和大家賣了個(gè)關(guān)子,其實(shí)大家說的都沒錯(cuò),因?yàn)檫@里涉及到Spring MVC父子容器的知識(shí)點(diǎn)。
這個(gè)知識(shí)點(diǎn)是:在使用Spring MVC過程中會(huì)存在Spring MVC 、Spring兩個(gè)IOC容器,且Spring MVC是Spring的子容器。
那這個(gè)父子容器到底是什么呢?
為了保證我所說的權(quán)威性,而不是知識(shí)的二道販子,我將從Spring 官方文檔和源碼兩方面展開介紹。
2.Spring MVC父子容器
2.1 web.xml配置
還是先找程序入口,查看web.xml配置文件,找到Spring MVC相關(guān)配置。
<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>配置很簡(jiǎn)單,只是配置了一個(gè)類型為DispatcherServlet類型的Servlet,并設(shè)置了初始化參數(shù)。那DispatcherServlet是什么呢?
2.2 DispatcherServlet類介紹
查看API文檔
從繼承圖看出最終繼承自HttpServlet,其實(shí)就是一個(gè)普通的Servlet。那為什么這個(gè)Servlet就能完成Spring MVC一系列復(fù)雜的功能呢?繼續(xù)往下看。
2.3 DispatcherServlet工作流程
DispatcherServlet工作流程如下:
(1) 所有請(qǐng)求先發(fā)到DispacherServlet 。
(2) DispacherServlet根據(jù)請(qǐng)求地址去查詢相應(yīng)的Controller,然后返回給DispacherServlet。
(3) DispacherServlet得到Controller后,讓Controler處理相應(yīng)的業(yè)務(wù)邏輯。
(4) Controler處理處理完后將結(jié)果返回給DispacherServlet。
(5) DispacherServlet把得到的結(jié)果用視圖解析器解析后獲得對(duì)應(yīng)的頁面。
(6) DispacherServlet跳轉(zhuǎn)到解析后的頁面。
在整個(gè)過程中DispatcherServlet承當(dāng)了一個(gè)中心控制器的角色來處理各種請(qǐng)求。
2.4 DispatcherServlet上下文繼承關(guān)系
上圖來自Spring官網(wǎng):
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html從圖中可以看到DispatcherServlet里面有一個(gè) Servlet WebApplicationContext,繼承自 Root WebApplicationContext。
從上篇文章中我們知道WebApplicationContext其實(shí)就是一個(gè)IOC容器,root WebApplicationContext是Spring容器。
這說明DispatcherServlet中里創(chuàng)建了一個(gè)IOC容器并且這個(gè)容器繼承了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.結(jié)合圖和上述文字我們可以得出以下信息:
看到這里也許大家心中也許就明白文章開頭中我說的Spring MVC中的父子容器了,對(duì)那個(gè)問題也有了自己的判斷和答案。
當(dāng)然文章還沒有結(jié)束,畢竟這還僅限于對(duì)官方文檔的理解,為了進(jìn)一步驗(yàn)證,我們拿出終極武器:
閱讀源碼!2.5 DispatcherServlet源碼分析
本小節(jié)我們分為Spring MVC容器的創(chuàng)建和bean的獲取兩部分進(jìn)行分析。
2.5.1 Spring MVC容器的創(chuàng)建
前面分析到DispatcherServlet本質(zhì)上還是一個(gè)Servlet ,既然是Servlet ,了解Servlet生命周期的同學(xué)都知道Web 容器裝載Servlet第一步是執(zhí)行init()函數(shù),因此以DispatcherServlet 的init函數(shù)為突破口進(jìn)行分析。
@Override public final void init() throws ServletException {// 1.讀取init parameters 等參數(shù),其中就包括設(shè)置contextConfigLocation PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);//2.初始化servlet中使用的beaninitServletBean(); }在第1步讀取init parameter的函數(shù)最終會(huì)調(diào)用setContextConfigLocation()設(shè)置配置文件路徑。此處重點(diǎn)介紹initServletBean(),繼續(xù)跟蹤。
Override protected final void initServletBean() throws ServletException {//初始化webApplicationContextthis.webApplicationContext = initWebApplicationContext(); } protected WebApplicationContext initWebApplicationContext() {//1.獲得rootWebApplicationContextWebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;//2.如果還沒有webApplicatioinContext,創(chuàng)建webApplicationContextif (wac == null) {//創(chuàng)建webApplicationContextwac = createWebApplicationContext(rootContext);}return wac; }可以看到上面初始化webApplicationContext分為2步。
(1)獲取父容器rootWebApplicationContext。
(2)創(chuàng)建子容器。
我們先看看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。
認(rèn)真看過上篇文章的同學(xué)應(yīng)該記得這個(gè)屬性是在Spring初始化 容器initWebApplicationContext()函數(shù)中的第3步設(shè)置進(jìn)去的,取得的值即Spring IOC容器。
繼續(xù)看如何創(chuàng)建webApplicationContext。
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {return createWebApplicationContext((ApplicationContext) parent); } createWebApplicationContext(ApplicationContext parent) {//1.獲取WebApplicationContext實(shí)現(xiàn)類,此處其實(shí)就是XmlWebApplicationContextClass<?> contextClass = getContextClass();//生成XmlWebApplicationContext實(shí)例ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);//2.設(shè)置rootWebApplicationContext為父容器 wac.setParent(parent);//3.設(shè)置配置文件wac.setConfigLocation(getContextConfigLocation());//4.配置webApplicationContext.configureAndRefreshWebApplicationContext(wac);return wac; } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {//開始處理beanwac.refresh(); }看到這里同學(xué)們有沒有是曾相識(shí)的感覺。是的,這段邏輯和上篇文章創(chuàng)建Spring IOC的邏輯類似。
唯一不同的是在第2步會(huì)把Spring容器設(shè)置為自己的父容器。至于新建容器中bean的注冊(cè)、解析、實(shí)例化等流程和Spring IOC容器一樣都是交給XmlWebApplicationContext類處理,還沒有掌握的同學(xué)可以看上篇文章。
2.5.2 Spring MVC Bean的獲取
Spring MVC bean的獲取其實(shí)我們?cè)谏掀恼乱呀?jīng)介紹過,這次再單拎出來介紹一下,加深記憶。
protected <T> T doGetBean(// 獲取父BeanFactoryBeanFactory parentBeanFactory = getParentBeanFactory();//如果父容器不為空,且本容器沒有注冊(cè)此bean就去父容器中獲取beanif (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// 如果父容器有該bean,則調(diào)用父beanFactory的方法獲得該beanreturn (T) parentBeanFactory.getBean(nameToLookup,args);}//如果子容器注冊(cè)了bean,執(zhí)行一系列實(shí)例化bean操作后返回bean.//此處省略實(shí)例化過程.....return (T) bean; }上面代碼就可以對(duì)應(yīng)官方文檔中“如果子容器中找不到bean,就去父容器找”的解釋了。
3.小結(jié)
看完上面的介紹,相信大家對(duì)Spring MVC父子容器的概念都有所了解,現(xiàn)在我們分析文章開頭的問題。
如果spring.xml和spring-mvc.xml定義了相同id的bean會(huì)怎樣?假設(shè)id=test。
1.首先Spring 初始化,Spring IOC 容器中生成一個(gè)id為test bean實(shí)例。
2.Spring MVC開始初始化,生成一個(gè)id為test bean實(shí)例。
此時(shí),兩個(gè)容器分別有一個(gè)相同id的bean。那用起來會(huì)不會(huì)混淆?
答案是不會(huì)。
當(dāng)你在Spring MVC業(yè)務(wù)邏輯中使用該bean時(shí),Spring MVC會(huì)直接返回自己容器的bean。
當(dāng)你在Spring業(yè)務(wù)邏輯中使用該bean時(shí),因?yàn)樽尤萜鞯腷ean對(duì)父親是不可見的,因此會(huì)直接返回Spring容器中的bean。
雖然上面的寫法不會(huì)造成問題。但是在實(shí)際使用過程中,建議大家都把bean定義都寫在spring.xml文件中。
因?yàn)槭褂脝卫齜ean的初衷是在IOC容器中只存在一個(gè)實(shí)例,如果兩個(gè)配置文件都定義,會(huì)產(chǎn)生兩個(gè)相同的實(shí)例,造成資源的浪費(fèi),也容易在某些場(chǎng)景下引發(fā)缺陷。
4.尾聲
現(xiàn)在大家基本都不使用在xml文件中定義bean的形式,而是用注解來定義bean,然后在xml文件中定義掃描包。如下:
<context:component-scan base-package="xx.xx.xx"/>那如果在spring.xml和spring-mvc.xml配置了重復(fù)的包會(huì)怎樣呢?
如果本文看明白的同學(xué)此時(shí)已經(jīng)知道了答案。
答案是會(huì)在兩個(gè)父子IOC容器中生成大量的相同bean,這就會(huì)造成內(nèi)存資源的浪費(fèi)。
也許有同學(xué)想到,那只在spring.xml中設(shè)置掃描包不就能避免這種問題發(fā)生了嗎,答案是這樣嗎?
大家可以試試,這樣會(huì)有什么問題。如果不行,那是為什么呢?
欲知分曉,敬請(qǐng)期待下篇分解!
如果想獲得更多,歡迎關(guān)注公眾號(hào):七分熟pizza
公眾號(hào)里我會(huì)分享更多技術(shù)以及職場(chǎng)方面的經(jīng)驗(yàn),大家有什么問題也可以直接在公眾號(hào)向我提問交流。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的boot spring 没有父子容器_Spring 系列(二):Spring MVC的父子容器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python lib库_python_l
- 下一篇: do与mysql数据类型对照_dopho