MVC 應用配置實現
-
在目前的實現中Spring的上下文容器可以稱之爲
RootApplicationContext
,而MVC的上下文容器稱之爲WebApplicationContext
或者ServletApplicationContext
。兩者是父子容器的關係。 -
Spring MVC 框架的核心類是
DispatcherServlet
,幾乎所有的工作都是圍繞DispatcherServlet
展開的。每一個DispatcherServlet
都有一個自己的WebApplicationContext
。該上下文容器的創建與初始化是在DispatcherServlet
類的創建和初始化過程中完成的。 -
由於Servlet3.x支持通過類完成應用容器初始化,創建
DispatcherServlet
一般會有兩種不同的方式:
- Web.xml 中配置實現
- 應用容器初始化接口的實現類中編碼實現,如
SpringServletContainerInitializer
類。 具體見 Servlet 筆記(12):Servlet3.X 版本新特性
Web.xml 配置實現
- Web.xml 配置實現
- 定義
DispatcherServlet
對象 - 通過初始化參數
contextConfigLocation
自定義配置文件,多個配置文件可以使用逗號、空格等分隔。如spring-mvc.xml
<servlet>
<servlet-name>dispatcherServlet</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>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
- 在指定的配置文件中(spring-mvc.xml),定義需要的Bean組件實現具體的功能。
Servlet3.x 容器實現
-
Servlet3.x 中可以不使用
Web.xml
,實現動態註冊Servlet
。MVC容器初始化以及DispatcherServlet
類的創建是在AbstractDispatcherServletInitializer#registerDispatcherServlet
方法中編碼完成,省去了配置定義DispatcherServlet
的步驟 -
在創建MVC的上下文容器中,有需要實現的
getServletConfigClasses
方法,可以設置Spring MVC 的配置類,需要自定義指定該Servlet
的ServletMappings
映射
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.learning.springmvc.base.javaconfig", useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(classes = Controller.class)})
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/pages/", ".jsp");
}
}
# Servlet3.x 初始化
public class WebMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
# Spring 容器配置
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{ApplicationConfig.class};
}
# MVC 容器配置
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebMvcConfig.class};
}
# 配置Servlet 的Mappings映射
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
MVC 容器初始化
DispatcherServlet 創建
- 在Spring MVC 的設計中,每一個
DispatcherServlet
應該有屬於自己的上下文容器,DispatcherServlet
有兩個構造函數,兩種構造正好對應着Web.xml
配置和SpringServletContainerInitializer
初始化類配置兩種不同的實現方式。
構造函數 | 應用初始化方式 | 解析 |
---|---|---|
無參構造 | Web.xml |
創建DispatcherServlet 對象,然後通過實現的接口方法initServletBean() 創建和初始化容器WebApplicationContext |
有參構造 | SpringServletContainerInitializer |
1、參數爲WebApplicationContext 對象,即先創建了容器對象WebApplicationContext 2、創建 DispatcherServlet 對象,通過實現的接口方法initServletBean() 初始化容器WebApplicationContext |
SpringServletContainerInitializer
方式創建上下文容器會先調用AbstractDispatcherServletInitializer#registerContextLoaderListener
方法創建Spring的上下文容器對象、ContextLoaderListener
對象,然後創建MVC的上下文容器以及DispatcherServlet
對象
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
# 創建Spring的上下文容器以及ContextLoaderListener對象
super.onStartup(servletContext);
# 創建 MVC 上下文容器以及 DispatcherServlet 對象
registerDispatcherServlet(servletContext);
}
- 在創建 MVC 上下文容器以及
DispatcherServlet
對象過程中,簡單總結爲如下步驟
編號 | 操作 | 解析 |
---|---|---|
1 | 創建Servlet 名稱 |
默認名稱爲 dispatcher |
2 | 創建 MVC 上下文容器 | 1、在創建容器對象的方法中,可以獲取容器相關的配置類。其方法getServletConfigClasses ,框架中沒有實現,需要應用擴展使用。2、創建的該上下文容器,類型爲 AnnotationConfigWebApplicationContext ,沒有設置任何屬性,如contextClass 、contextConfigLocation 等 |
3 | 創建DispatcherServlet 對象 |
1、可以獲取DispatcherServlet 對應的映射,其方法getServletMappings ,框架中沒有實現,需要應用擴展使用2、把創建的容器對象作爲參數創建 DispatcherServlet 對象,並且把創建的該對象動態註冊到ServletContext 中 |
4 | 自定義容器設置 | 獲取自定義接口ApplicationContextInitializer 的實現,目前該功能沒有任何實現。 |
5 | 動態註冊過濾器以及其他自定義組件 | 往ServletContext 中動態註冊Filter 、自定義組件等。框架本身沒有具體的實現,需要通過覆蓋getServletFilters 、customizeRegistration 方法自定義實現 |
protected void registerDispatcherServlet(ServletContext servletContext) {
# 默認servlet 名稱爲 dispatcher
String servletName = getServletName();
# 創建MVC上下文容器
WebApplicationContext servletAppContext = createServletApplicationContext();
# 創建`DispatcherServlet`對象
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
# 設置初始化類 ApplicationContextInitializer。沒有任何實現
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
# 動態把該Servlet 註冊到 `servletContext` 上下文
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());# 映射
registration.setAsyncSupported(isAsyncSupported());
# 動態註冊 Filter,可以重寫 getServletFilters 實現
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
# 動態自定義註冊其他的,重寫 customizeRegistration 方法實現
customizeRegistration(registration);
}
DispatcherServlet 初始化
HttpServlet
初始化調用了HttpServletBean#init
方法,該方法在Servlet初始化中被調用,主要工作就是MVC容器的初始化工作
- 該方法還會獲取
Servlet
中的init
參數,並且創建一個BeanWrapper
對象,然後由子類真正執行BeanWrapper
的初始化工作。由於HttpServletBean
的子類都沒有覆蓋其initBeanWrapper
方法,所以創建的BeanWrapper
對象沒有任何作用
public final void init() throws ServletException {
// 設置初始化參數
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
... ...
}
// 初始化Web 上下文容器,初始化 Servlet這個Bean
initServletBean();
... ...
}
@Override
protected final void initServletBean() throws ServletException {
# 初始化Web 上下文容器
this.webApplicationContext = initWebApplicationContext();
# 初始化 Servlet,這個方法暫時實現爲空。
initFrameworkServlet();
}
- 在方法
initWebApplicationContext()
中完成的MVC上下文容器創建初始化過程,簡單總結爲如下4個步驟:
編號 | 步驟 | 分析 |
---|---|---|
1 | 設置父容器 | 如果MVC容易已經存在,則從 ServletContext 中獲取key爲org.springframework.web.context.WebApplicationContext.ROOT 的Spring上下文容器,並把他設置爲MVC上下文容器的父容器 |
2 | 初始化容器 | 如果MVC容易已經存在,並判斷容器是否成功初始化(isActive 狀態),如未初始化,則執行容器的初始化工作 |
3 | 創建容器 | 判斷上下文容器對象是否已經創建,如果沒有則創建一個新的容器對象 |
4 | Serevlet個性化刷新工作 | 該方法內部用來初始化MVC框架的核心組件,由於在MVC上下文容器初始化時使用spring 的事件機制完成了個性化刷新工作(refreshEventReceived 狀態),所以這邊一般不會再次執行。 |
5 | 保存容器 | 把初始化的MVC容器作爲一個屬性存入ServletContext 中,key爲 org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher 。dispatcher 爲默認的Servlet的名稱。 |
protected WebApplicationContext initWebApplicationContext() {
# 從 ServletContext 中獲取Spring上下文容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
# 如果MVC容易已經存在
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
# 把應用上下文容器設置成 Web 上下文容器的父容器
cwac.setParent(rootContext);
}
# 配置和刷新(初始化)Web 上下文容器
configureAndRefreshWebApplicationContext(cwac);
}
}
}
# 如果MVC容易不存在
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 該方法在DispatcherServlet中覆蓋重寫,用於初始化mvc內部核心組件.其核心方法在於 DispatcherServlet#initStrategies() 方法
// 其實在 刷新(初始化)MVC上下文容器時利用spring的事件機制,已經初始化過一遍,這邊不會走。
onRefresh(wac);
}
if (this.publishContext) {
# 把Web上下文容器,存在 ServletContext 中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
... ...
}
return wac;
}
- 容器創建方法
createWebApplicationContext
只有在使用Web.xml 配置方式時纔會被調用。
- 先獲取創建上下文容器的類型,默認是
XmlWebApplicationContext
,然後利用反射直接創建。 - 設置上下文容器相關的屬性,如父容器、
contextConfigLocation
等 - 初始化刷新該容器對象
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
# 獲取創建上下文容器的類型
Class<?> contextClass = getContextClass();
... ...
# 反射創建
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
# 設置各種屬性參數
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
# 初始化容器
configureAndRefreshWebApplicationContext(wac);
return wac;
}
- 上下文容器配置與初始化方法
configureAndRefreshWebApplicationContext()
是整個過程的核心。所有初始化的工作部分都是在這邊完成的,主要有:
編號 | 任務名稱 | 具體事項 |
---|---|---|
1 | 設置容器的唯一Id | 1、可以使用初始化參數contextId 自定義2、默認值爲一個組合值,爲 WebApplicationContext 類的全路徑名稱加上ServletName 的值。默認容器ID爲org.springframework.web.context.WebApplicationContext:/dispatcher |
2 | 設置一般屬性 | 屬性包括 ServletContext 、ServletConfig 、Namespace 、ApplicationListener ,值得關注的爲添加了自定義的ContextRefreshListener 容器刷新監聽器。 |
3 | 初始化佔位符屬性源 | 這個操作在容器初始化(刷新操作)始終會被執行。這邊執行的原因在於確保servlet屬性源在刷新操作之前的任何後處理或初始化中都處於適當的位置 |
4 | 後置處理 | 在刷新並激活給定的上下文容器作爲此該Seervlet的上下文容器之前,對其進行後處理。默認實現爲空,需要子類擴展實現 |
5 | 自定義容器設置 | 1、在容器初始化刷新之前自定義創建的容器 2、該自定義擴展功能專門由一個接口 ApplicationContextInitializer 實現3、通過初始化屬性 contextInitializerClasses ,globalInitializerClasses ,指定自定義的ApplicationContextInitializer 接口實現類器4、通過這個功能可以在容器對象初始化之前,擴展容器的功能 |
6 | 上下文初始化 | 調用AbstractApplicationContext#refresh 方法完成初始化。具體見 Spring4.x 筆記(6):Ioc 容器高級-內部工作機制 |
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
# 設置 contextId
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// 默認值
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
# 設置一般屬性
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
# 初始化佔位符屬性源。
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
# 自定義後置處理
postProcessWebApplicationContext(wac);
# 自定義容器設置
applyInitializers(wac);
# 上下文初始化刷新
wac.refresh();
}
- 上下文容器初始化刷新後,會提交一個容器刷新事件。而在
DispatcherServlet
父類自定義的容器刷新事件監聽中ContextRefreshListener
,直接調用FrameworkServlet#onRefresh
方法初始負責初始化內部組件,並且設置refreshEventReceived
屬性爲true
。
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
onRefresh(event.getApplicationContext());
}
至此整個DispatcherServlet
以及其所屬的MVC上下文容器完成創建和初始化,可以接收HTTP請求了。