文档章节

SpringMVC4.x源码分析(二):DispatcherServlet初始化过程

祖大俊
 祖大俊
发布于 2018/06/09 15:31
字数 1829
阅读 625
收藏 2

DispatcherServlet的类继承图。

(Made In IntelliJ IDEA)

DispatcherServlet是一个Servlet,那么它就遵循Servlet的生命周期。如上图所示,DispatcherServlet还实现了Spring IOC的Aware接口,了解Aware接口的人都知道,Spring在创建对象的时候,会自动注入Aware接口方法里的对象。比如上图,会自动给DispatcherServlet注入Environment和ApplicationContext对象,如果你这么认为,那就大错特错了,只能说明你Spring学的不错。DispatcherServlet对象由Web容器(Tomcat)来管理,并不由Spring IOC管理,因此,根本就不可能自动注入Environment和ApplicationContext对象。这里的ApplicationContextAware和EnvironmentAware实际是作为普通接口使用,需要手动编程调用接口方法。

简化后的DispatcherServlet类继承图。

(Made In IntelliJ IDEA)

在了解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内。

(Made In IntelliJ IDEA)

继续看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;
	}

看过几篇SpringMVC源码分析的文章,均未能很好的解答我对上述源码的疑问,最后通过自己的努力,总算逐一解开谜团,我们再对上述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对象。

我们使用的入门例子,不存在父子容器,继续看创建WebApplicationContext的过程。

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 />标签解析器完成。

下一节,我们将分析<mvc:annotation-driven />和<context:component-scan/>标签,都干了些什么事情。

原文出处:http://my.oschina.net/zudajun

© 著作权归作者所有

祖大俊
粉丝 802
博文 32
码字总数 52477
作品 0
昌平
私信 提问
加载中

评论(2)

你说你喜欢雨天_而我姓杨
你说你喜欢雨天_而我姓杨

引用来自“你说你喜欢雨天_而我姓杨”的评论

我这类继承图和你差异很大啊
看错了,不好意思
你说你喜欢雨天_而我姓杨
你说你喜欢雨天_而我姓杨
我这类继承图和你差异很大啊
Spring MVC 原理探秘 - 一个请求的旅行过程

1.简介 在前面的文章中,我较为详细的分析了 Spring IOC 和 AOP 部分的源码,并写成了文章。为了让我的 Spring 源码分析系列文章更为丰富一些,所以从本篇文章开始,我将来向大家介绍一下 Sp...

coolblog.xyz
2018/07/02
0
0
Spring MVC 原理探秘 - 容器的创建过程

1.简介 在上一篇文章中,我向大家介绍了 Spring MVC 是如何处理 HTTP 请求的。Spring MVC 可对外提供服务时,说明其已经处于了就绪状态。再次之前,Spring MVC 需要进行一系列的初始化操作。...

coolblog.xyz
2018/07/03
0
0
浅谈SpringMVC执行过程

通过深入分析Spring源码,我们知道Spring框架包括大致六大模块, 如Web模块,数据库访问技术模块,面向切面模块,基础设施模块,核心容器模块和模块, 其中,在Spring框架的Web模块中,又包含...

编程SHA
03/08
47
0
SpringMVC源码剖析(三)- DispatcherServlet的初始化流程

在我们第一次学Servlet编程,学java web的时候,还没有那么多框架。我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转到我们定义好的...

相见欢
2013/01/15
19K
21
Spring3.2 MVC 分析

Spring3.2 MVC 分析: SpringMVC现在应该用得很广泛了,其配置清晰,灵活度,定制能力等都是很强的,相比Struts2也是胜过一筹,还是从源码来分析一下,SpringMVC为我们做了什么。 先从配置文...

ihaolin
2014/02/03
3.1K
2

没有更多内容

加载失败,请刷新页面

加载更多

栈-链式(c/c++实现)

上次说“栈是在线性表演变而来的,线性表很自由,想往哪里插数据就往哪里插数据,想删哪数据就删哪数据...。但给线性表一些限制呢,就没那么自由了,把线性表的三边封起来就变成了栈,栈只能...

白客C
11分钟前
10
0
Mybatis Plus service

/** * @author beth * @data 2019-10-20 23:34 */@RunWith(SpringRunner.class)@SpringBootTestpublic class ServiceTest { @Autowired private IUserInfoService iUserInfoS......

一个yuanbeth
19分钟前
2
0
php7-internal 7 zval的操作

## 7.7 zval的操作 扩展中经常会用到各种类型的zval,PHP提供了很多宏用于不同类型zval的操作,尽管我们也可以自己操作zval,但这并不是一个好习惯,因为zval有很多其它用途的标识,如果自己...

冻结not
昨天
5
0
溢出\越界\泄漏

溢出:栈溢出是指函数中的局部变量造成的溢出,递归次数太多也会栈溢出 一是分配的大小超过栈的最大值,char a[99999999999999999]; 二是分配的大小没有超过最大值,但是接收的buff比新buff小...

SibylY
昨天
5
0
导览Linux系统文件系统类型

虽然对于普通用户来说可能并不明显,但在过去十年左右的时间里,Linux 文件系统已经发生了显著的变化,这使它们能够更好对抗损坏和性能问题。 如今大多数 Linux 系统使用名为 ext4 的文件系统...

老孟的Linux私房菜
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部