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请求了。