目录
前言
springmvc的核心是DispatcherServlet,所以整个初始化也是围绕它展开的。他是springmvc的所有类的父类,一系列的功能都是围绕这个核心类展开。(上图是DispatcherServlet类图)
servlet标准定义了init()方法是它的生命周期的初始化方法:
HttpServletBean.init
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//包装DispatcherServlet,准备放入容器
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//用以加载spring-mvc配置文件
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//没有子类实现此方法
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
// Let subclasses do whatever initialization they like.
initServletBean();
}
逻辑已经注释的很清晰。其中,setPropertyValues方法会导致对DispatcherServlet相关setter方法的调用,所以当进行容器初始化时从init-param中读取的参数已被设置到DispatcherServlet的相关字段(filed)中。
容器初始化
FrameworkServlet.initServletBean
简略版源码:
@Override
protected final void initServletBean() {
this.webApplicationContext = initWebApplicationContext();
//空实现,且没有子类覆盖
initFrameworkServlet()
}
FrameworkServlet.initWebAppplicationContext
:
protected WebApplicationContext initWebApplicationContext() {
//根容器查找
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
//有可能DispatcherServlet被作为Spring bean初始化,且webApplicationContext已被注入进来
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//是否已经存在于ServletContext中
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
onRefresh(wac);
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
根容器查找
spring-mvc支持spring容器与mvc容器共存,因此spring容器即根容器,mvc容器将根容器视为父容器。
Spring容器(根容器)以下列形式进行配置(web.xml):
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
根据servlet的规范,各组件的加载顺序如下:
listener --> filter --> servlet
WebApplicationContextUtils.getWebApplicationContext
:
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
两参数方法:
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
}
return (WebApplicationContext) attr;
}
我们得出一个结论:
如果spring根容器存在,那么它被保存在ServletContext中,其key为WebApplicationContext.class.getName()+".ROOT"
。
容器创建
FrameworkContext.createWebApplicationContext
:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException();
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
通过对getContextClass方法的调用,Spring 允许我们自定义容器的类型,也就是说我们可以在web.xml中进行如下配置:
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置文件位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-servlet.xml</param-value>
</init-param>
<!-- 容器类型 -->
<init-param>
<param-name>contextClass</param-name>
<param-value>java.lang.Object</param-value>
</init-param>
</servlet>
configureAndRefrashWebApplicationContext核心源码:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
applyInitializers(wac);
wac.refresh();
}
ApplicationContextInitializer
ApplicationContextInitializer允许我们在Spring(mvc)容器初始化前干点坏事,可以通过init-param传入:
<init-param>
<param-name>contextInitializerClasses</param-name>
<param-value>坏事儿</param-value>
</init-param>
applyInitializer方法是要触发这些坏事儿。
ApplicationContextInitializer类图:
配置解析
“配置”指spring-servlet.xml
<context:component-scan base-package="controller"/>
<mvc:annotation-driven/>
<!-- 启用对静态资源使用默认servlet处理,非REST方式不需要 -->
<mvc:default-servlet-handler/>
<!-- 配置视图 -->
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<!-- viewClass属性必不可少 -->
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
而解析的入口便在于refresh方法的调用,此方法位于AbstractApplicationContext。
对spring-mvc来说,其容器默认为XmlWebApplicationContext,部分类图:
XmlWebApplicationContext通过重写loadBeanDefinitions方法改变了bean加载行为,使其指向spring-servlet.xml。
spring-servlet.xml中不同于spring-core的地方便在于引入了mvc命名空间,Spring用过jar包/META-INFO中的.handlers文件定义针对不同的命名空间所使用的解析器。
mvc命名空间的解析器为MvcNamespaceHandler,部分源码:
@Override
public void init() {
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("default-servlet-handler",
new DefaultServletHandlerBeanDefinitionParser());
registerBeanDefinitionParser("interceptors", new IanterceptorsBeanDefinitionParser());
registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
}
注解驱动
其parse方法负责向Sprng容器注册一些必要的组件,如下图:
静态资源处理
即:
<mvc:default-servlet-handler/>
-
DefaultServletHandlerBeanDefinitionParser.parse负责向容器注册以下三个组件:
- DefaultServletHttpRequestHandler
- SimpleUrlHandlerMapping
- HttpRequestHandlerAdapter
拦截器
InterceptorsBeanDefinitionParser.parse方法负责将每一项mvc:interceptor
配置解析为一个MappedInterceptor bean并注册到容器中。
视图
-
有两种方式向Spring容器注册视图:
- 以前采用较土的方式:
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<!-- viewClass属性必不可少 -->
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
: 通过特定的标签:
<mvc:view-resolvers>
<mvc:jsp view-class="" />
</mvc:view-resolvers>
从这里可以推测出: 拦截器同样支持第一种方式,Spring在查找时应该会查询某一接口的子类。
ViewResolversBeanDefinitionParser.parse方法的作用便是将每一个视图解析为ViewResolver并注册到容器。
Scope/处理器注册
AbstractRefreshableWebApplicationContext.postProcessBeanFactory:
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(
new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
this.servletContext, this.servletConfig);
}
ServletContextAwareProcessor用以向实现了ServletContextAware的bean注册ServletContext。
registerWebApplicationScopes用以注册"request", “session”, “globalSession”, "application"四种scope。
registerEnvironmentBeans用以将servletContext、servletConfig以及各种启动参数注册到Spring容器中。
MVC初始化
入口位于DispatcherServlet的initStrategies方法(经由onRefresh调用):
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
显然,这里就是spring-mvc的核心了。
文件上传支持
initMultipartResolver核心源码:
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
} catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
}
}
MultipartResolver用于开启Spring MVC文件上传功能,其类图:
也就是说,如果我们要使用文件上传功能,须在容器中注册一个MultipartResolver bean。当然,默认是没有的。
地区解析器
LocaleResolver接口定义了Spring MVC如何获取客户端(浏览器)的地区,initLocaleResolver方法在容器中寻找此bean,如果没有,注册AcceptHeaderLocaleResolver,即根据request的请求头Accept-Language获取地区。
spring-mvc采用了属性文件的方式配置默认策略(即bean),此文件位于spring-mvc的jar包的org.springframework.web.servlet下。
主题解析器
ThemeResolver接口配合Spring标签库使用可以通过动态决定使用的css以及图片的方式达到换肤的效果,其类图:
如果容器中不存在叫做themeResolver的bean,initThemeResolver方法将向容器中注册FixedThemeResolver,此bean只能提供一套默认的主题,名为theme。
HandlerMapping检查
initHandlerMappings方法用于确保容器中至少含有一个HandlerMapping对象。从前面配置解析-注解驱动一节中可以看出,注解驱动导致已经注册了两个此对象。
如果没有开启注解驱动,那么将会使用默认的HandlerMapping,相关源码:
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
前面提到了,默认的策略由DispatcherServlet.properties决定,目前是BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping。
HandlerAdapter检查
套路和上面完全一样,默认使用HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter。
HandlerExceptionResolver检查
套路和上面完全一样,默认使用AnnotationMethodHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver。
RequestToViewNameTranslator
initRequestToViewNameTranslator方法回向容器中注册一个DefaultRequestToViewNameTranslator对象,此接口用以完成从HttpServletRequest到视图名的解析,其使用场景是给定的URL无法匹配任何控制器时。
DefaultRequestToViewNameTranslator的转换例子:
http://localhost:8080/gamecast/display.html -> display(视图)
其类图: