標籤:網上 location pps ade shared 傳統 https map embed
spring mvc裡的root/child WebApplicationContext的繼承關係
在傳統的spring mvc程式裡會有兩個WebApplicationContext,一個是parent,從applicationContext.xml裡載入的,一個是child,從servlet-context.xml裡載入的。
兩者是繼承關係,child WebApplicationContext 能夠通過getParent()函數擷取到root WebApplicationContext。
簡單地說child WebApplicationContext裡的bean能夠注入root WebApplicationContext裡的bean,而parent WebApplicationContext的bean則不能注入child WebApplicationContext裡的bean。
一個典型的web.xml的內容是:
<!-- The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:/applicationContext.xml</param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
當中root WebApplicationContext是通過listener初始化的,child WebApplicationContext是通過servlet初始化的。
而在applicationContext.xml裡通常僅僅component-scan非Controller的類,如:
<context:component-scan base-package="io.github.test"> <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation" /> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" /> </context:component-scan>
在servlet-context.xml裡通常僅僅component-scan Controller類,如:
<context:component-scan base-package="io.github.test.web" use-default-filters="false"> <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation" /> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" /> </context:component-scan>
假設不這樣子分別component-scan的話,可能會出現Bean反覆初始化的問題。
上面是Spring官方開始時推薦的做法。
root/child WebApplicationContext繼承關係帶來的麻煩
root WebApplicationContext裡的bean能夠在不同的child WebApplicationContext裡共用,而不同的child WebApplicationContext裡的bean區不干擾。這個本來是個非常好的設計。
可是實際上有會不少的問題:
* 不少開發人員不知道Spring mvc裡分有兩個WebApplicationContext。導致各種反覆構造bean,各種bean無法注入的問題。
* 有一些bean,比方全域的aop處理的類,假設先root WebApplicationContext裡初始化了,那麼child WebApplicationContext裡的初始化的bean就沒有處理到。
假設在child WebApplicationContext裡初始化,在root WebApplicationContext裡的類就沒有辦法注入了。
* 區分哪些bean放在root/child非常麻煩。不小心easy搞錯,並且費心思。
一勞永逸的解決的方法:bean都由root WebApplicationContext載入
在一次配置metrics-spring時。對配置@EnableMetrics配置在哪個WebApplicationContext裡,感到非常蛋疼。
終於決定試下把全部的bean,包含Controller都移到root WebApplicationContext。即applicationContext.xml裡載入,而servlet-context.xml裡基本是空的。結果發現程式執行全然沒問題。
後面在網上搜尋了下。發現有一些相關的討論:
http://forum.spring.io/forum/spring-projects/container/89149-servlet-context-vs-application-context
spring boot裡的做法
在spring boot裡預設情況下不須要component-scan的配置,於是推測在Spring boot裡是不是僅僅有一個WebApplicationContext?
後面測試下了,發如今spring boot裡預設情況下的確是僅僅有一個WebApplicationContext:org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext,所以在spring boot裡省事了非常多。
總結
spring 的ApplicationContext繼承機制是一個非常好的設計,在非常多其他地方都能夠看到相似的思路。比方Java的class loader。可是在大部分spring web程式裡,實際上僅僅要一個WebApplicationContext就夠了。假設分開rott/child WebApplicationContext會導致混亂,而沒什麼用。
所以推薦把全部的Service/Controller都移到root WebApplicationContext中初始化。
扯談spring mvc之WebApplicationContext的繼承關係