上面我們分析了IOC容器本身的實現,下面我們看看在典型的web環境中,Spring IOC容器是怎樣被載入和起作用的。
簡單的說,在web容器中,通過ServletContext為Spring的IOC容器提供宿主環境,對應的建立起一個IOC容器的體系。其中,首先需要建立的是根上下文,這個上下文持有的對象可以有業務對象,數據存取對象,資源,事物管理器等各種中間層對象。在這個上下文的基礎上,和web MVC相關還會有一個上下文來保存控制器之類的MVC對象,這樣就構成了一個層次化的上下文結構。在web容器中啟動Spring應用程序就是一個建立這個上下文體系的過程。Spring為web應用提供了上下文的擴展接口
WebApplicationContext:
- public interface WebApplicationContext extends ApplicationContext {
-
- String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
- ......
-
- ServletContext getServletContext();
- }
public interface WebApplicationContext extends ApplicationContext {
//這里定義的常量用于在ServletContext中存取根上下文
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
......
//對WebApplicationContext來說,需要得到Web容器的ServletContext
ServletContext getServletContext();
}
而一般的啟動過程,Spring會使用一個默認的實現,XmlWebApplicationContext - 這個上下文實現作為在web容器中的根上下文容器被建立起來,具體的建立過程在下面我們會詳細分析。
- public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
-
-
- public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
- public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
- public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
-
-
- protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
-
- XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
-
- beanDefinitionReader.setResourceLoader(this);
- beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
-
- initBeanDefinitionReader(beanDefinitionReader);
- loadBeanDefinitions(beanDefinitionReader);
- }
-
- protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
- }
-
- protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
- String[] configLocations = getConfigLocations();
- if (configLocations != null) {
- for (int i = 0; i < configLocations.length; i++) {
- reader.loadBeanDefinitions(configLocations[i]);
- }
- }
- }
-
- protected String[] getDefaultConfigLocations() {
- if (getNamespace() != null) {
- return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
- }
- else {
- return new String[] {DEFAULT_CONFIG_LOCATION};
- }
- }
- }
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
/** 這是和web部署相關的位置信息,用來作為默認的根上下文bean定義信息的存放位置*/
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
//我們又看到了熟悉的loadBeanDefinition,就像我們前面對IOC容器的分析中一樣,這個加載工程在容器的refresh()的時候啟動。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
//對于XmlWebApplicationContext,當然使用的是XmlBeanDefinitionReader來對bean定義信息來進行解析
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
}
//使用XmlBeanDefinitionReader來讀入bean定義信息
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (int i = 0; i < configLocations.length; i++) {
reader.loadBeanDefinitions(configLocations[i]);
}
}
}
//這里取得bean定義信息位置,默認的地方是/WEB-INF/applicationContext.xml
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
}
對于一個Spring激活的web應用程序,可以通過使用Spring代碼聲明式的指定在web應用程序啟動時載入應用程序上下文(WebApplicationContext),Spring的ContextLoader是提供這樣性能的類,我們可以使用 ContextLoaderServlet或者ContextLoaderListener的啟動時載入的Servlet來實例化Spring IOC容器 - 為什么會有兩個不同的類來裝載它呢,這是因為它們的使用需要區別不同的Servlet容器支持的Serlvet版本。但不管是 ContextLoaderSevlet還是 ContextLoaderListener都使用ContextLoader來完成實際的WebApplicationContext的初始化工作。這個ContextLoder就像是Spring Web應用程序在Web容器中的加載器booter。當然這些Servlet的具體使用我們都要借助web容器中的部署描述符來進行相關的定義。
下面我們使用ContextLoaderListener作為載入器作一個詳細的分析,這個Servlet的監聽器是根上下文被載入的地方,也是整個 Spring web應用加載上下文的第一個地方;從加載過程我們可以看到,首先從Servlet事件中得到ServletContext,然后可以讀到配置好的在web.xml的中的各個屬性值,然后ContextLoder實例化WebApplicationContext并完成其載入和初始化作為根上下文。當這個根上下文被載入后,它被綁定到web應用程序的ServletContext上。任何需要訪問該ApplicationContext的應用程序代碼都可以從WebApplicationContextUtils類的靜態方法來得到:
- WebApplicationContext getWebApplicationContext(ServletContext sc)
WebApplicationContext getWebApplicationContext(ServletContext sc)
以Tomcat作為Servlet容器為例,下面是具體的步驟:
1.Tomcat 啟動時需要從web.xml中讀取啟動參數,在web.xml中我們需要對ContextLoaderListener進行配置,對于在web應用啟動入口是在ContextLoaderListener中的初始化部分;從Spring MVC上看,實際上在web容器中維護了一系列的IOC容器,其中在ContextLoader中載入的IOC容器作為根上下文而存在于 ServletContext中。
-
- public void contextInitialized(ServletContextEvent event) {
-
- this.contextLoader = createContextLoader();
-
- this.contextLoader.initWebApplicationContext(event.getServletContext());
- }
//這里對根上下文進行初始化。
public void contextInitialized(ServletContextEvent event) {
//這里創建需要的ContextLoader
this.contextLoader = createContextLoader();
//這里使用ContextLoader對根上下文進行載入和初始化
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
通過ContextLoader建立起根上下文的過程,我們可以在ContextLoader中看到:
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
- throws IllegalStateException, BeansException {
-
- if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
-
- .........
- }
-
- ...............
- try {
-
- ApplicationContext parent = loadParentContext(servletContext);
-
-
-
- this.context = createWebApplicationContext(servletContext, parent);
- servletContext.setAttribute(
- WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
- ..........
-
- return this.context;
- }
- ............
- }
public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
throws IllegalStateException, BeansException {
//這里先看看是不是已經在ServletContext中存在上下文,如果有說明前面已經被載入過,或者是配置文件有錯誤。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
//直接拋出異常
.........
}
...............
try {
// 這里載入根上下文的父上下文
ApplicationContext parent = loadParentContext(servletContext);
//這里創建根上下文作為整個應用的上下文同時把它存到ServletContext中去,注意這里使用的ServletContext的屬性值是
//ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,以后的應用都是根據這個屬性值來取得根上下文的 - 往往作為自己上下文的父上下文
this.context = createWebApplicationContext(servletContext, parent);
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
..........
return this.context;
}
............
}
建立根上下文的父上下文使用的是下面的代碼,取決于在web.xml中定義的參數:locatorFactorySelector,這是一個可選參數:
- protected ApplicationContext loadParentContext(ServletContext servletContext)
- throws BeansException {
-
- ApplicationContext parentContext = null;
-
- String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
- String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
-
- if (locatorFactorySelector != null) {
- BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
- ........
-
- this.parentContextRef = locator.useBeanFactory(parentContextKey);
-
- parentContext = (ApplicationContext) this.parentContextRef.getFactory();
- }
-
- return parentContext;
- }
protected ApplicationContext loadParentContext(ServletContext servletContext)
throws BeansException {
ApplicationContext parentContext = null;
String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
if (locatorFactorySelector != null) {
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
........
//得到根上下文的父上下文的引用
this.parentContextRef = locator.useBeanFactory(parentContextKey);
//這里建立得到根上下文的父上下文
parentContext = (ApplicationContext) this.parentContextRef.getFactory();
}
return parentContext;
}
得到根上下文的父上下文以后,就是根上下文的創建過程:
- protected WebApplicationContext createWebApplicationContext(
- ServletContext servletContext, ApplicationContext parent) throws BeansException {
-
-
- Class contextClass = determineContextClass(servletContext);
- .........
-
- ConfigurableWebApplicationContext wac =
- (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
-
- wac.setParent(parent);
- wac.setServletContext(servletContext);
-
-
- String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
- if (configLocation != null) {
- wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,
- ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
- }
-
- wac.refresh();
- return wac;
- }
protected WebApplicationContext createWebApplicationContext(
ServletContext servletContext, ApplicationContext parent) throws BeansException {
//這里需要確定我們載入的根WebApplication的類型,由在web.xml中配置的contextClass中配置的參數可以決定我們需要載入什么樣的ApplicationContext,
//如果沒有使用默認的。
Class contextClass = determineContextClass(servletContext);
.........
//這里就是上下文的創建過程
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//這里保持對父上下文和ServletContext的引用到根上下文中
wac.setParent(parent);
wac.setServletContext(servletContext);
//這里從web.xml中取得相關的初始化參數
String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocation != null) {
wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,
ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
//這里對WebApplicationContext進行初始化,我們又看到了熟悉的refresh調用。
wac.refresh();
return wac;
}
初始化根ApplicationContext后將其存儲到SevletContext中去以后,這樣就建立了一個全局的關于整個應用的上下文。這個根上下文會被以后的DispatcherServlet初始化自己的時候作為自己ApplicationContext的父上下文。這個在對 DispatcherServlet做分析的時候我們可以看看到。
3.完成對ContextLoaderListener的初始化以后, Tomcat開始初始化DispatchServlet,- 還記得我們在web.xml中隊載入次序進行了定義。DispatcherServlet會建立自己的ApplicationContext,同時建立這個自己的上下文的時候會從ServletContext中得到根上下文作為父上下文,然后再對自己的上下文進行初始化,并最后存到 ServletContext中去供以后檢索和使用。
可以從DispatchServlet的父類FrameworkServlet的代碼中看到大致的初始化過程,整個ApplicationContext的創建過程和ContextLoder創建的過程相類似:
- protected final void initServletBean() throws ServletException, BeansException {
- .........
- try {
-
- this.webApplicationContext = initWebApplicationContext();
-
- initFrameworkServlet();
- }
- ........
- }
protected final void initServletBean() throws ServletException, BeansException {
.........
try {
//這里是對上下文的初始化過程。
this.webApplicationContext = initWebApplicationContext();
//在完成對上下文的初始化過程結束后,根據bean配置信息建立MVC框架的各個主要元素
initFrameworkServlet();
}
........
}
對initWebApplicationContext()調用的代碼如下:
- protected WebApplicationContext initWebApplicationContext() throws BeansException {
-
- WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
-
-
- WebApplicationContext wac = createWebApplicationContext(parent);
- ........
- if (isPublishContext()) {
-
- String attrName = getServletContextAttributeName();
- getServletContext().setAttribute(attrName, wac);
- }
- return wac;
- }
protected WebApplicationContext initWebApplicationContext() throws BeansException {
//這里調用WebApplicationContextUtils靜態類來得到根上下文
WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
//創建當前DispatcherServlet的上下文,其上下文種類使用默認的在FrameworkServlet定義好的:DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
WebApplicationContext wac = createWebApplicationContext(parent);
........
if (isPublishContext()) {
//把當前建立的上下文存到ServletContext中去,注意使用的屬性名是和當前Servlet名相關的。
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
其中我們看到調用了WebApplicationContextUtils的靜態方法得到根ApplicationContext:
- public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
-
- Object attr = sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
- .......
- return (WebApplicationContext) attr;
- }
- 然后創建DispatcherServlet自己的WebApplicationContext:
- protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
- throws BeansException {
- .......
-
-
- ConfigurableWebApplicationContext wac =
- (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());
-
-
- wac.setParent(parent);
-
-
- wac.setServletContext(getServletContext());
- wac.setServletConfig(getServletConfig());
- wac.setNamespace(getNamespace());
-
-
- if (getContextConfigLocation() != null) {
- wac.setConfigLocations(
- StringUtils.tokenizeToStringArray(
- getContextConfigLocation(), ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
- }
-
-
- wac.refresh();
- return wac;
- }
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
//很簡單,直接從ServletContext中通過屬性名得到根上下文
Object attr = sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
.......
return (WebApplicationContext) attr;
}
然后創建DispatcherServlet自己的WebApplicationContext:
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
throws BeansException {
.......
//這里使用了BeanUtils直接得到WebApplicationContext,ContextClass是前面定義好的DEFAULT_CONTEXT_CLASS =
//XmlWebApplicationContext.class;
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());
//這里配置父上下文,就是在ContextLoader中建立的根上下文
wac.setParent(parent);
//保留ServletContext的引用和相關的配置信息。
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
//這里得到ApplicationContext配置文件的位置
if (getContextConfigLocation() != null) {
wac.setConfigLocations(
StringUtils.tokenizeToStringArray(
getContextConfigLocation(), ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
//這里調用ApplicationContext的初始化過程,同樣需要使用refresh()
wac.refresh();
return wac;
}
4. 然后就是DispatchServlet中對Spring MVC的配置過程,首先對配置文件中的定義元素進行配置 - 請注意這個時候我們的WebApplicationContext已經建立起來了,也意味著DispatcherServlet有自己的定義資源,可以需要從web.xml中讀取bean的配置信息,通常我們會使用單獨的xml文件來配置MVC中各個要素定義,這里和web容器相關的加載過程實際上已經完成了,下面的處理和普通的Spring應用程序的編寫沒有什么太大的差別,我們先看看MVC的初始化過程:
- protected void initFrameworkServlet() throws ServletException, BeansException {
- initMultipartResolver();
- initLocaleResolver();
- initThemeResolver();
- initHandlerMappings();
- initHandlerAdapters();
- initHandlerExceptionResolvers();
- initRequestToViewNameTranslator();
- initViewResolvers();
- }
protected void initFrameworkServlet() throws ServletException, BeansException {
initMultipartResolver();
initLocaleResolver();
initThemeResolver();
initHandlerMappings();
initHandlerAdapters();
initHandlerExceptionResolvers();
initRequestToViewNameTranslator();
initViewResolvers();
}
5. 這樣MVC的框架就建立起來了,DispatchServlet對接受到的HTTP Request進行分發處理由doService()完成,具體的MVC處理過程我們在doDispatch()中完成,其中包括使用Command模式建立執行鏈,顯示模型數據等,這些處理我們都可以在DispatcherServlet的代碼中看到:
- protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
- ......
- try {
- doDispatch(request, response);
- }
- .......
- }
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
......
try {
doDispatch(request, response);
}
.......
}
實際的請求分發由doDispatch(request,response)來完成:
- protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception {
- .......
-
- HandlerExecutionChain mappedHandler = null;
-
- ......
- try {
-
- ModelAndView mv = null;
- try {
- processedRequest = checkMultipart(request);
-
-
- mappedHandler = getHandler(processedRequest, false);
-
- ......
-
- HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
- mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
- .......
-
- if (mv != null && !mv.wasCleared()) {
- render(mv, processedRequest, response);
- }
- ........
- }
protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception {
.......
// 這是Spring定義的執行鏈,里面放了映射關系對應的handler和定義的相關攔截器。
HandlerExecutionChain mappedHandler = null;
......
try {
//我們熟悉的ModelAndView在這里出現了。
ModelAndView mv = null;
try {
processedRequest = checkMultipart(request);
//這里更具request中的參數和映射關系定義決定使用的handler
mappedHandler = getHandler(processedRequest, false);
......
//這里是handler的調用過程,類似于Command模式中的execute.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
.......
//這里將模型數據通過視圖進行展現
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
}
........
}
這樣具體的MVC模型的實現就由bean配置文件里定義好的view resolver,handler這些類來實現用戶代碼的功能。
總結上面的過程,我們看到在web容器中,ServletContext可以持有一系列的web上下文,而在整個web上下文中存在一個根上下文來作為其它 Servlet上下文的父上下文。這個根上下文是由ContextLoader載入并進行初始化的,對于我們的web應用, DispatcherSerlvet載入并初始化自己的上下文,這個上下文的父上下文是根上下文,并且我們也能從ServletContext中根據 Servlet的名字來檢索到我們需要的對應于這個Servlet的上下文,但是根上下文的名字是由Spring唯一確定的。這個 DispactcherServlet建立的上下文就是我們開發Spring MVC應用的IOC容器。
具體的web請求處理在上下文體系建立完成以后由DispactcherServlet來完成,上面對MVC的運作做了一個大致的描述,下面我們會具體就SpringMVC的框架實現作一個詳細的分析。