SpringMVC之DispatcherServlet初始化过程
DispatcherServlet的类继承图。
DispatcherServlet是一个Servlet,那么它就遵循Servlet的生命周期。如上图所示,DispatcherServlet还实现了Spring IOC的Aware接口,了解Aware接口的人都知道,Spring在创建对象的时候,会自动注入Aware接口方法里的对象。比如上图,会自动给DispatcherServlet注入Environment和ApplicationContext对象,如果你这么认为,那就大错特错了,只能说明你Spring学的不错。
DispatcherServlet对象由Web容器(Tomcat)来管理,并不由Spring IOC管理,因此,根本就不可能自动注入Environment和ApplicationContext对象。
这里的ApplicationContextAware和EnvironmentAware实际是作为普通接口使用,需要手动编程调用接口方法。
在了解DispatcherServlet的init()初始化方法之前,先了解它的static静态代码块。
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
private static final Properties defaultStrategies;
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
//...
}
}
静态代码块会读取DispatcherServlet.properties
配置文件,该配置文件配置了默认的SpringMVC需要使用的一系列组件,当没有配置<mvc:annotation-driven />标签时,这些默认配置才会生效,很显然,我们已经配置了<mvc:annotation-driven />标签。
DispatcherServlet.properties文件内容:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
再次强调,SpringMVC通常不会使用这些默认的甚至过时的配置,添加<mvc:annotation-driven />标签,该标签会为我们注册当前最优秀的MVC组件,后面我们会分析到。
Servlet创建时,会执行init()初始化方法,看HttpServletBean.init()。
@Override
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);
}
catch (BeansException ex) {
throw ex;
}
}
// 初始化入口方法
initServletBean();
}
上面的源码,完成了从servletConfig取值
、给当前HttpServletBean对象属性赋值
、调用初始化入口方法
三个功能。
我们写一个简单列子,演示一下,读者便可立马明白上面的代码逻辑。
User user = new User();
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(user);
PropertyValue pv = new PropertyValue("name", "张三");
bw.setPropertyValue(pv);
System.out.println(user.getName());
output:张三
由此可见,DispatcherServlet的contextConfigLocation属性就有值了,该属性定义在FrameworkServlet内。
继续看FrameworkServlet.initServletBean()方法:
protected final void initServletBean() throws ServletException {
//...
try {
this.webApplicationContext = initWebApplicationContext();
// 空方法
initFrameworkServlet();
}
//...
}
其实就是创建了一个Spring的WebApplicationContext对象,称之为web应用上下文,存储在DispatcherServlet中。
FrameworkServlet#initWebApplicationContext()方法源码:
protected WebApplicationContext initWebApplicationContext() {
// ①、使用ContextLoaderListener所加载的Web应用上下文
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// ②、使用Servlet构造函数注册的Web应用上下文,Servlet3.0+API使用
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) {
// ③、使用contextAttribute值指定的ServletContext上下文中的Web应用上下文
wac = findWebApplicationContext();
}
if (wac == null) {
// ④、DispatcherServlet中init-param指定的contextConfigLocation所加载的Web应用上下文
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// ⑤、初始化SpringMVC的基础组件
onRefresh(wac);
}
if (this.publishContext) {
// ⑥、将Web应用上下文,存储在ServletContext上下文中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
我们再对上述6条注释进行详细说明。
注:这里所谓的Web应用上下文,指的是Spring的WebApplicationContext对象。
①、使用ContextLoaderListener所加载的Web应用上下文,并不陌生,web.xml中构造父子容器的常见方案。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:/applicationContext.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
②、使用Servlet构造函数注册的Web应用上下文,好像没听说过,其实呢是Servlet3.0+编程式创建Servlet时使用。
/**
* Create a new {@code DispatcherServlet} with the given web application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based registration
* of servlets is possible through the {@link ServletContext#addServlet} API.
**/
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}
③、使用contextAttribute值指定的ServletContext上下文中的Web应用上下文。
例如:从ServletContext上下文中去找一个key为myWebApplicationContext的Web应用上下文,来作为DispatcherServlet对象中的webApplicationContext属性的值,当然,前提是ServletContext上下文中放置过该Web应用上下文对象。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>myWebApplicationContext</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
④、DispatcherServlet中init-param指定的contextConfigLocation所加载的Web应用上下文,也就是我们入门例子中配置的唯一Web应用上下文,重点关注。
⑤、初始化SpringMVC的基础组件。
如果配置了<mvc:annotation-driven />标签,则使用<mvc:annotation-driven />标签所绑定的SpringMVC基础组件。如果没有配置<mvc:annotation-driven />标签,则使用DispatcherServlet.properties配置文件内默认的SpringMVC基础组件 。我们的入门例子配置了<mvc:annotation-driven />标签。
⑥、将Web应用上下文,存储在ServletContext上下文中
本例key=org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherServlet
动作:getServletContext().setAttribute(key, wac);
至此,webApplicationContext
对象,存在于DispatcherServlet对象中,也存在于ServletContext上下文中,ServletContext对象,就是传说中的global session,也就是jsp中的application对象。
webApplicationContext上下文对象中,也存储了servletContext和servletConfig对象。
FrameworkServlet#createWebApplicationContext(org.springframework.context.ApplicationContext)
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;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//...
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);
// 执行web.xml中init-param指定的ApplicationContextInitializer的实现类初始化方法
applyInitializers(wac);
// Spring容器的刷新方法
wac.refresh();
}
wac.refresh()是Spring容器的启动刷新方法,它会扫描<context:component-scan base-package="com.spring"/>所指定目录下的@Component,@Service,@Controller,@Repository,@Configuration所标注的类,大多数博文都遗漏了@Configuration,其实,@Service,@Controller,@Repository,@Configuration,都是@Component,它们自身都被@Component所标注。
至此,webApplicationContext就创建完毕了。
回过头来,我们再看看DispatcherServlet#onRefresh(wac)收尾方法。
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
从方法名上,可以看到,后面带s的代表有多个,不带s的代表单个,我们以initHandlerMappings()为例:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// 找所有的HandlerMappings,包含祖先容器上下文.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 找名字为handlerMapping的HandlerMapping对象,包含祖先容器上下文.
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
if (this.handlerMappings == null) {
// 如果没有配置,取DispatcherServlet.properties中的默认配置
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
getBean()是返回第一个HandlerMapping对象,包含祖先容器。
而BeanFactoryUtils.beansOfTypeIncludingAncestors()方法是返回所有HandlerMapping对象,包含祖先容器,是一个集合。
如果没有配置,取DispatcherServlet.properties中的默认配置。本例中,没有祖先容器,也不会取DispatcherServlet.properties的默认配置,因为我们配置了<mvc:annotation-driven />标签。
至此,DispatcherServlet的初始化过程就完成了,初始化过程,主要完成了两个功能:
1、创建并完成启动刷新webApplicationContext上下文对象。
2、注册SpringMVC的八大组件,并从Controllor中解析出每个HandlerMethod,由<mvc:annotation-driven />标签解析器完成。
Servlet 容器与 Spring 容器有什么关系?
Tomcat&Jetty在启动时给每个Web应用创建一个全局的上下文环境,这个上下文就是ServletContext,其为后面的Spring容器提供宿主环境。
Tomcat&Jetty在启动过程中触发容器初始化事件,Spring的ContextLoaderListener会监听到这个事件,它的contextInitialized方法会被调用,在这个方法中,Spring会初始化全局的Spring根容器,这个就是Spring的IoC容器,IoC容器初始化完毕后,Spring将其存储到ServletContext中,便于以后来获取。
Tomcat&Jetty在启动过程中还会扫描Servlet,一个Web应用中的Servlet可以有多个,以SpringMVC中的DispatcherServlet为例,这个Servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个Servlet请求。
Servlet一般会延迟加载,当第一个请求达到时,Tomcat&Jetty发现DispatcherServlet还没有被实例化,就调用DispatcherServlet的init方法,DispatcherServlet在初始化的时候会建立自己的容器,叫做SpringMVC 容器,用来持有Spring MVC相关的Bean。同时,Spring MVC还会通过ServletContext拿到Spring根容器,并将Spring根容器设为SpringMVC容器的父容器,请注意,Spring MVC容器可以访问父容器中的Bean,但是父容器不能访问子容器的Bean, 也就是说Spring根容器不能访问SpringMVC容器里的Bean。说的通俗点就是,在Controller里可以访问Service对象,但是在Service里不可以访问Controller对象。