博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringMVC4.x源码分析(二):DispatcherServlet初始化过程
阅读量:6573 次
发布时间:2019-06-24

本文共 10861 字,大约阅读时间需要 36 分钟。

hot3.png

DispatcherServlet的类继承图。

8e1f085fe230316976c5ec6eb7e99aa02a3.jpg

(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类继承图。

5b6667bc1528844f063e1b560f096e47aa6.jpg

(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()。

@Overridepublic 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内。

51de27f7823c564f08eb4210b7161df6c78.jpg

(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中构造父子容器的常见方案。

contextConfigLocation
classpath:/applicationContext.xml
org.springframework.web.context.ContextLoaderListener

②、使用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应用上下文对象。

dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextAttribute
myWebApplicationContext
1

④、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对象。

daf3389f5572f8e3e6247de59c6849b5c91.jpg

我们使用的入门例子,不存在父子容器,继续看创建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,,,,@Configuration所标注的类,大多数博文都遗漏了@Configuration,其实,,,,@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
matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList
(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

转载于:https://my.oschina.net/zudajun/blog/1827341

你可能感兴趣的文章
Spring3.0核心组件的源码简单分析
查看>>
如何成功清理重建CloudStack环境
查看>>
什么是Linq
查看>>
cacti 安装与配置
查看>>
EmEditor小功能与使用技巧
查看>>
VC6.0不支持标准库函数max和min
查看>>
添加WSS3.0中文模板
查看>>
shell脚本:监控MySQL服务是否正常
查看>>
Silverlight实用窍门系列:40.Silverlight中捕捉视频,截图保存到本地
查看>>
sudo报错案例-RHEL6
查看>>
“减少风险”还是“管理风险”哪一根才是救命稻草?
查看>>
MySQL5.6基于GTID同步复制,与如何实现MySQL负载均衡、读写分离。
查看>>
ASP.NET 动态输出Javascript 文本格式换行问题 [ASP.NET | C# | Response]
查看>>
Android第三十三期 - Dialog的应用
查看>>
如何编程实现iAMT无线功能的禁用和开启
查看>>
在通往VR内容的道路上,音乐将成为一项重要助力
查看>>
【网络基础】《TCP/IP详解》学习笔记2
查看>>
在Winhex中搜索文本字符时注意
查看>>
机器视觉系统工作流程及优势分析
查看>>
QC入门简介
查看>>