關於兩個應用上下
《Spring實戰(第四版)》中有一段程式碼如下:
package spittr.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; import spittr.web.WebConfig; public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { RootConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { WebConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
這其實也是實際專案中常用的一段程式碼,這裡有兩個非常相似的方法getRootConfigClasses()和getServletConfigClasses(),分別用於指定Root配置和Web配置。其中,Web配置類一般配置控制器、檢視解析器等Web相關的Bean,而Root配置中定義業務邏輯、資料存取之類的其他Bean。在Spring官網文件裡也提到了兩個WebApplicationContext,一個是Servlet WebApplicationContext,一個是Root WebApplicationContext,前面程式碼的兩個方法返回的配置類其實就是分別用於在這兩個“上下文”中建立物件。(把Context看成“容器”要容易理解一些)一般來說,配置類通常還要通過元件掃描載入其他Bean,所以如果Bean、Component、Controller這些放錯了包,或者元件掃描定義錯誤,有時候會出現執行時錯誤,有時候不會出現錯誤但是並不是希望的結果。因為Spring Bean裝配都是執行時才裝配,編譯時不一定出錯。當然,可以把所有的配置類也即所有的元件都放在一個context裡,應該是可以的,無非是一個容器,所有東西都放在一起,就不會出現找不到的情況了,要是還找不到那肯定是程式碼本身有問題,漏掉了一些東西,而不會是Bean裝配問題。的確,Spring文件裡面也說,當Servlet在Servlet WebApplicationContext裡找不到物件時,就會到Root WebApplicationContext裡去找,甚至可以完全把所有東西都委託delegates給Root WebApplicationContext。既然如此,那為什麼還要弄兩個“上下文”呢?
這個問題我的理解是:Spring是模組化的,Spring的Core功能也就是依賴注入、切面這些功能,並不一定非得要和Spring MVC一起才能用,也許有些人已經習慣了其他框架,比如JSF,仍然可以使用Spring的其他功能的。這時,就沒有Spring MVC也就沒有Servlet WebApplicationContext了。如果用JSF作為框架,Spring的初始化是通過在Web.xml中定義的listener即org.springframework.web.context.ContextLoaderListener來進行的,這個類實現了Java EE定義的ServletContextListener介面,web容器在啟動應用時會建立一個ServletContext用於Servlet與Web容器進行互動,然後呼叫 ServletContextListener介面的方法,ContextLoaderListener這個類就在這時初始化Spring的執行時環境(Context本意應該是執行時環境)這個Context就放在ServletContext裡面,所以Servlet裡可以訪問得到。這個WebApplicationContext是Spring最初的Context,所以叫Root WebApplicationContext,這個時候連DispatcherServlet都還沒有創建出來!同樣道理,由於模組化的原因,為確保DispatcherServlet總是能夠工作,org.springframework.web.servlet.DispatcherServlet這個類也沒有完全依賴ContextLoaderListener建立的環境,它的建構函式有兩個,當傳遞一個WebApplicationContext給它時,它就用這個“上下文”,這樣就只有一個“上下文”,否則,它就自己建立一個,也就是Servlet WebApplicationContex,這樣就有兩個“上下文”。我覺得《Spring實戰》中的建議不錯的,把web相關的控制器、檢視解析器、解碼器、轉換器這些放在Servlet WebApplicationContext裡,web無關的放在Root WebApplicationContext這樣結構性好一點,只是要注意配置元件掃描不要混亂。當然,包的結構清不清楚跟用幾個上下文並沒有多大關係,所有Bean都放在Root WebApplicationContext也行,其實都放在Servlet WebApplicationContex裡也是可以的,因為一般來說包括Spring在內的框架都只有一個分派DispatcherServlet了,把Servlet建立的Contex實也就是整個Web應用程式的執行時環境也沒什麼不可。只是要知道一個Web應用可以有若干個Servlet的,所以要想把所有Bean放在一起,最好還是放Root WebApplicationContext裡。
另外,剛才講到了兩個“上下文”,也說了只有一個“上下文”的可能性,但是從AbstractAnnotationConfigDispatcherServletInitializer這個類的繼承樹來看AbstractAnnotationConfigDispatcherServletInitializer->AbstractDispatcherServletInitializer->AbstractContextLoaderInitializer->WebApplicationInitializer,通過實現AbstractAnnotationConfigDispatcherServletInitializer類來初始化Spring Web應用程式的話,兩個“上下文”都是建立好了的,要只建立一個“上下文”那隻能是實現AbstractContextLoaderInitializer,但這時已經建立好的“上下文”仍然是Root WebApplicationContext,要想只有Servlet WebApplicationContex還不太容易,必須自己實現WebApplicationInitializer這個介面,要做的事太多。所以,兩個“上下文”就兩個“上下文”吧,明白上述道理後只需要知道把所有配置類都放在getRootConfigClasses、 getServletConfigClasses()其中之一是完全可以的,如果分開放,則需要知道ServletConfig裡面的Bean可以通過注入引用RootConfig裡面的Bean,反之則不行,因為構建Root WebApplicationContext的時候還沒有Servlet更沒有Servlet WebApplicationContex。而Spring的依懶注入在找不到Bean情況下就會建立一個,這樣就有可能在程式程式碼裡面只定義了一個Bean,編譯不會出問題,但執行時,在兩個上下文裡面各有一個例項。本來只應用有一個的,現在多出一個來,程式執行起來當然達不到目的,這種錯誤很不好排查,跟蹤除錯看到的程式碼都是對頭的,就是值不一樣,呵呵。