簡述Spring容器與SpringMVC的容器的聯(lián)系與區(qū)別

在Spring整體框架的核心概念中饼煞,容器的核心思想是管理Bean的整個(gè)生命周期痹束。但在一個(gè)項(xiàng)目中宇色,Spring容器往往不止一個(gè),最常見的場景就是在一個(gè)項(xiàng)目中引入Spring和SpringMVC這兩個(gè)框架施禾,其本質(zhì)就是兩個(gè)容器:Spring是根容器脚线,SpringMVC是其子容器。關(guān)于這兩個(gè)容器的創(chuàng)建弥搞、聯(lián)系及區(qū)別也正是本文所關(guān)注的問題邮绿。

喜歡本文、覺得本文對您有幫助的朋友別忘了點(diǎn)點(diǎn)贊和關(guān)注攀例。持續(xù)更新更多干貨

一船逮、引子

Spring和SpringMVC作為Bean管理容器和MVC層的默認(rèn)框架,已被眾多web應(yīng)用采用粤铭。但是在實(shí)際應(yīng)用中傻唾,初級開發(fā)者常常會因?qū)pring和SpringMVC的配置失當(dāng)導(dǎo)致一些奇怪的異常現(xiàn)象,比如Controller的方法無法攔截冠骄、Bean被多次加載等問題,這種情況發(fā)生的根本原因在于開發(fā)者對Spring容器和SpringMVC容器之間的關(guān)系了解不夠深入加袋,這也正是本文要闡述的問題凛辣。

二、Spring容器职烧、SpringMVC容器與ServletContext之間的關(guān)系

在Web容器中配置Spring時(shí)扁誓,你可能已經(jīng)司空見慣于web.xml文件中的以下配置代碼,下面我們以該代碼片段為基礎(chǔ)來了解Spring容器蚀之、SpringMVC容器與ServletContext之間的關(guān)系蝗敢。要想理解這三者的關(guān)系,需要先熟悉Spring是怎樣在web容器中啟動起來的足删。Spring的啟動過程其實(shí)就是其Spring IOC容器的啟動過程寿谴。特別地,對于web程序而言失受,IOC容器啟動過程即是建立上下文的過程讶泰。

<web-app>...<!-- 利用Spring提供的ContextLoaderListener監(jiān)聽器去監(jiān)聽ServletContext對象的創(chuàng)建,并初始化WebApplicationContext對象 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- Context Configuration locations for Spring XML files(默認(rèn)查找/WEB-INF/applicationContext.xml) --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!-- 配置Spring MVC的前端控制器:DispatchcerServlet --><servlet><servlet-name>SpringMVC</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>SpringMVC</servlet-name><url-pattern>/</url-pattern></servlet-mapping>...</web-app>

Spring的啟動過程

對于一個(gè)web應(yīng)用拂到,其部署在web容器中痪署,web容器提供其一個(gè)全局的上下文環(huán)境,這個(gè)上下文就是ServletContext兄旬,其為后面的spring IoC容器提供宿主環(huán)境狼犯;

在web.xml中會提供有contextLoaderListener。在web容器啟動時(shí)领铐,會觸發(fā)容器初始化事件悯森,此時(shí)contextLoaderListener會監(jiān)聽到這個(gè)事件,其contextInitialized方法會被調(diào)用罐孝。在這個(gè)方法中呐馆,spring會初始化一個(gè)啟動上下文,這個(gè)上下文被稱為根上下文莲兢,即WebApplicationContext汹来。WebApplicationContext是一個(gè)接口類,確切的說改艇,其實(shí)際的實(shí)現(xiàn)類是XmlWebApplicationContext收班,它就是spring的IoC容器,其對應(yīng)的Bean定義的配置由web.xml中的<context-param>標(biāo)簽指定谒兄。在這個(gè)IoC容器初始化完畢后摔桦,Spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE為屬性Key,將其存儲到ServletContext中,便于獲攘诟鸥咖;

ContextLoaderListener監(jiān)聽器初始化完畢后泊愧,開始初始化web.xml中配置的Servlet豁延,這個(gè)servlet可以配置多個(gè)壁查,以最常見的DispatcherServlet為例援雇,這個(gè)servlet實(shí)際上是一個(gè)標(biāo)準(zhǔn)的前端控制器团搞,用以轉(zhuǎn)發(fā)立莉、匹配茉稠、處理每個(gè)servlet請求累榜。DispatcherServlet上下文在初始化的時(shí)候會建立自己的IoC上下文削解,用以持有spring mvc相關(guān)的bean富弦。特別地,在建立DispatcherServlet自己的IoC上下文前氛驮,會利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先從ServletContext中獲取之前的根上下文(即WebApplicationContext)作為自己上下文的parent上下文腕柜。有了這個(gè)parent上下文之后,再初始化自己持有的上下文柳爽。這個(gè)DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到媳握,大概的工作就是初始化處理器映射、視圖解析等磷脯。這個(gè)servlet自己持有的上下文默認(rèn)實(shí)現(xiàn)類也是mlWebApplicationContext蛾找。初始化完畢后,spring以與servlet的名字相關(guān)(此處不是簡單的以servlet名為Key赵誓,而是通過一些轉(zhuǎn)換打毛,具體可自行查看源碼)的屬性為屬性Key,也將其存到ServletContext中俩功,以便后續(xù)使用幻枉。這樣每個(gè)servlet就持有自己的上下文,即擁有自己獨(dú)立的bean空間诡蜓,同時(shí)各個(gè)servlet共享相同的bean熬甫,即根上下文(第2步中初始化的上下文)定義的那些bean。

Spring容器與SpringMVC的容器聯(lián)系與區(qū)別

ContextLoaderListener中創(chuàng)建Spring容器主要用于整個(gè)Web應(yīng)用程序需要共享的一些組件蔓罚,比如DAO椿肩、數(shù)據(jù)庫的ConnectionFactory等;而由DispatcherServlet創(chuàng)建的SpringMVC的容器主要用于和該Servlet相關(guān)的一些組件豺谈,比如Controller郑象、ViewResovler等。它們之間的關(guān)系如下:

作用范圍

子容器(SpringMVC容器)可以訪問父容器(Spring容器)的Bean茬末,父容器(Spring容器)不能訪問子容器(SpringMVC容器)的Bean厂榛。也就是說,當(dāng)在SpringMVC容器中g(shù)etBean時(shí),如果在自己的容器中找不到對應(yīng)的bean击奶,則會去父容器中去找辈双,這也解釋了為什么由SpringMVC容器創(chuàng)建的Controller可以獲取到Spring容器創(chuàng)建的Service組件的原因。

具體實(shí)現(xiàn)

在Spring的具體實(shí)現(xiàn)上柜砾,子容器和父容器都是通過ServletContext的setAttribute方法放到ServletContext中的辐马。但是,ContextLoaderListener會先于DispatcherServlet創(chuàng)建ApplicationContext局义,DispatcherServlet在創(chuàng)建ApplicationContext時(shí)會先找到由ContextLoaderListener所創(chuàng)建的ApplicationContext,再將后者的ApplicationContext作為參數(shù)傳給DispatcherServlet的ApplicationContext的setParent()方法冗疮。也就是說萄唇,子容器的創(chuàng)建依賴于父容器的創(chuàng)建,父容器先于子容器創(chuàng)建术幔。在Spring源代碼中另萤,你可以在FrameServlet.java中找到如下代碼:

wac.setParent(parent);

其中,wac即為由DisptcherServlet創(chuàng)建的ApplicationContext诅挑,而parent則為有ContextLoaderListener創(chuàng)建的ApplicationContext四敞。此后,框架又會調(diào)用ServletContext的setAttribute()方法將wac加入到ServletContext中拔妥。

三忿危、Spring容器和SpringMVC容器的配置

在Spring整體框架的核心概念中,容器是核心思想没龙,就是用來管理Bean的整個(gè)生命周期的铺厨,而在一個(gè)項(xiàng)目中,容器不一定只有一個(gè)硬纤,Spring中可以包括多個(gè)容器解滓,而且容器間有上下層關(guān)系,目前最常見的一種場景就是在一個(gè)項(xiàng)目中引入Spring和SpringMVC這兩個(gè)框架筝家,其實(shí)就是兩個(gè)容器:Spring是根容器洼裤,SpringMVC是其子容器。在上文中溪王,我們提到腮鞍,SpringMVC容器可以訪問Spring容器中的Bean,Spring容器不能訪問SpringMVC容器的Bean在扰。但是缕减,若開發(fā)者對Spring容器和SpringMVC容器之間的關(guān)系了解不夠深入,常常會因配置失當(dāng)而導(dǎo)致同時(shí)配置Spring和SpringMVC時(shí)出現(xiàn)一些奇怪的異常芒珠,比如Controller的方法無法攔截桥狡、Bean被多次加載等問題。

在實(shí)際工程中,一個(gè)項(xiàng)目中會包括很多配置裹芝,根據(jù)不同的業(yè)務(wù)模塊來劃分部逮,我們一般思路是各負(fù)其責(zé),明確邊界嫂易,即:Spring根容器負(fù)責(zé)所有其他非controller的Bean的注冊兄朋,而SpringMVC只負(fù)責(zé)controller相關(guān)的Bean的注冊,下面我們演示這種配置方案怜械。

(1). Spring容器配置

Spring根容器負(fù)責(zé)所有其他非controller的Bean的注冊:

<!-- 啟用注解掃描颅和,并定義組件查找規(guī)則 ,除了@controller缕允,掃描所有的Bean --><context:component-scanbase-package="cn.edu.tju.rico"><context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Controller"/></context:component-scan>

(2). SpringMVC容器配置

SpringMVC只負(fù)責(zé)controller相關(guān)的Bean的注冊峡扩,其中@ControllerAdvice用于對控制器進(jìn)行增強(qiáng),常用于實(shí)現(xiàn)全局的異常處理類:

<!-- 啟用注解掃描障本,并定義組件查找規(guī)則 教届,mvc層只負(fù)責(zé)掃描@Controller、@ControllerAdvice --><!-- base-package 如果多個(gè)驾霜,用“,”分隔 --><context:component-scanbase-package="cn.edu.tju.rico"use-default-filters="false"><!-- 掃描@Controller --><context:include-filtertype="annotation"expression="org.springframework.stereotype.Controller"/><!--控制器增強(qiáng)案训,使一個(gè)Contoller成為全局的異常處理類,類中用@ExceptionHandler方法注解的方法可以處理所有Controller發(fā)生的異常 --><context:include-filtertype="annotation"expression="org.springframework.web.bind.annotation.ControllerAdvice"/></context:component-scan>

在<context:component-scan>中可以添加use-default-filters粪糙,Spring配置中的use-default-filters用來指示是否自動掃描帶有@Component强霎、@Repository、@Service和@Controller的類猜旬。默認(rèn)為true脆栋,即默認(rèn)掃描。如果想要過濾其中這四個(gè)注解中的一個(gè)洒擦,比如@Repository椿争,可以添加<context:exclude-filter />子標(biāo)簽,如下:

<context:component-scanbase-package="cn.edu.tju.rico"scoped-proxy="targetClass"use-default-filters="true">

<context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Repository"/> </context:component-scan>

而<context:include-filter/>子標(biāo)簽是用來添加掃描注解的:

<context:component-scanbase-package="cn.edu.tju.rico"scoped-proxy="targetClass"use-default-filters="false">

<context:include-filtertype="annotation"expression="org.springframework.stereotype.Repository"/> </context:component-scan>

四熟嫩、Spring容器和SpringMVC容器的配置失當(dāng)帶來的問題

問題描述

在一個(gè)項(xiàng)目中秦踪,想使用Spring AOP在Controller中切入一些邏輯,但發(fā)現(xiàn)不能切入到Controller的中掸茅,但可以切入到Service中椅邓。最初的配置情形如下:

1). Spring的配置文件application.xml包含了開啟AOP自動代理、Service掃描配置以及Aspect的自動掃描配置昧狮,如下所示:

<aop:aspectj-autoproxy/><context:component-scanbase-package="cn.edu.tju.rico">

2). Spring MVC的配置文件spring-mvc.xml主要內(nèi)容是Controller層的自動掃描配置景馁。

<context:component-scanbase-package="cn.edu.tju.rico.controller"/>

3). 增強(qiáng)代碼為如下:

@Component@AspectpublicclassSecurityAspect{privatestaticfinalStringDEFAULT_TOKEN_NAME="X-Token";privateTokenManagertokenManager;@Resource(name="tokenManager")publicvoidsetTokenManager(TokenManagertokenManager){this.tokenManager=tokenManager;}@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")publicObjectexecute(ProceedingJoinPointpjp)throwsThrowable{// 從切點(diǎn)上獲取目標(biāo)方法MethodSignaturemethodSignature=(MethodSignature)pjp.getSignature();Methodmethod=methodSignature.getMethod();// 若目標(biāo)方法忽略了安全性檢查,則直接調(diào)用目標(biāo)方法if(method.isAnnotationPresent(IgnoreSecurity.class)){System.out.println("method.isAnnotationPresent(IgnoreSecurity.class) : "+method.isAnnotationPresent(IgnoreSecurity.class));returnpjp.proceed();}// 從 request header 中獲取當(dāng)前 tokenStringtoken=WebContext.getRequest().getHeader(DEFAULT_TOKEN_NAME);// 檢查 token 有效性if(!tokenManager.checkToken(token)){Stringmessage=String.format("token [%s] is invalid",token);thrownewTokenException(message);}// 調(diào)用目標(biāo)方法returnpjp.proceed();}}

4). 需要被代理的Controller如下:

@RestController@RequestMapping("/tokens")publicclassTokenController{privateUserService userService;privateTokenManager tokenManager;publicUserServicegetUserService(){returnuserService;}@Resource(name="userService")publicvoidsetUserService(UserService userService){this.userService=userService;}publicTokenManagergetTokenManager(){returntokenManager;}@Resource(name="tokenManager")publicvoidsetTokenManager(TokenManager tokenManager){this.tokenManager=tokenManager;}@RequestMapping(method=RequestMethod.POST)@IgnoreSecuritypublicResponselogin(@RequestParam("uname")String uname,@RequestParam("passwd")String passwd){boolean flag=userService.login(uname,passwd);if(flag){String token=tokenManager.createToken(uname);System.out.println("**** Token **** : "+token);returnnewResponse().success("Login Success...");}returnnewResponse().failure("Login Failure...");}@RequestMapping(method=RequestMethod.DELETE)@IgnoreSecuritypublicResponselogout(@RequestParam("uname")String uname){tokenManager.deleteToken(uname);returnnewResponse().success("Logout Success...");}}

在運(yùn)行過程中逗鸣,發(fā)現(xiàn)這樣配置并沒有起作用合住,AOP配置不生效绰精,沒有生成TokenController的代理。

解決方案

由上一節(jié)可知透葛,原因有兩點(diǎn):

Spring容器與SpringMVC容器分別基于各自的配置文件進(jìn)行初始化笨使,所以,在SpringMVC容器創(chuàng)建TokenController時(shí)僚害,由于其沒有啟用AOP代理硫椰,導(dǎo)致SpringMVC容器沒有為TokenController生成代理,所以沒有生效萨蚕。

雖然父容器啟用了AOP代理靶草,但由于父子容器的獨(dú)立性,無濟(jì)于事岳遥。

因此爱致,我們只需要在SpringMVC的配置文件中添加Aspect的自動掃描配置即可實(shí)現(xiàn)所要的效果。此外寒随,一般地,SpringMVC容器只管理Controller帮坚,剩下的Service妻往、Repository 和 Component 由Spring容器只管理,不建議兩個(gè)容器上在管理Bean上發(fā)生交叉试和。因此讯泣,建議配置為:

SpringMVC 配置:

<aop:aspectj-autoproxy/><context:component-scanbase-package="com.hodc.sdk.controller"/>

Spring配置:

<context:annotation-config/><context:component-scanbase-package="com.hodc.sdk"><context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Controller"/></context:component-scan>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市阅悍,隨后出現(xiàn)的幾起案子好渠,更是在濱河造成了極大的恐慌,老刑警劉巖节视,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拳锚,死亡現(xiàn)場離奇詭異,居然都是意外死亡寻行,警方通過查閱死者的電腦和手機(jī)霍掺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拌蜘,“玉大人杆烁,你說我怎么就攤上這事〖蛭裕” “怎么了兔魂?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長举娩。 經(jīng)常有香客問我析校,道長构罗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任勺良,我火速辦了婚禮绰播,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尚困。我一直安慰自己蠢箩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布事甜。 她就那樣靜靜地躺著谬泌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逻谦。 梳的紋絲不亂的頭發(fā)上掌实,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機(jī)與錄音邦马,去河邊找鬼贱鼻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滋将,可吹牛的內(nèi)容都是我干的邻悬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼随闽,長吁一口氣:“原來是場噩夢啊……” “哼父丰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掘宪,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蛾扇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后魏滚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镀首,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年鼠次,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蘑斧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡须眷,死狀恐怖竖瘾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情花颗,我是刑警寧澤捕传,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站扩劝,受9級特大地震影響庸论,放射性物質(zhì)發(fā)生泄漏职辅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一聂示、第九天 我趴在偏房一處隱蔽的房頂上張望域携。 院中可真熱鬧,春花似錦鱼喉、人聲如沸秀鞭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锋边。三九已至,卻和暖如春编曼,著一層夾襖步出監(jiān)牢的瞬間豆巨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工掐场, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留往扔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓熊户,卻偏偏與公主長得像瓤球,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子敏弃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容