SpringMVC源码深度分析DispatcherServlet核心的控制器(初始化)

SpringMVC是非常优秀的MVC框架,每个框架都是为了我们提高开发效率,我们试图通过对SpringMVC的源代码去了解这个框架,了解整个设计思想,框架要有扩展性,这里用的比较多是接口和抽象,是框架的主力,我们通过了解源代码能对SpringMVC框架更了解,也能对我们开发思想有很大的启发。

SpringMVC由几个核心类和接口组成的,我们今天要的一个是DispatcherServlet核心的前置控制器,配置在Web.xml中,所以请求都经过它来统一分发的。SpringMVC几个核心类和接口都会出现在DispatcherServlet的源码中,我这里大概介绍一个,今天重点是介绍DispatcherServlet核心的前置控制器,后面我们在详细分析其它的几个核心类和接口分析。

DispatcherServlet的继承关系图,能清晰的了解整个层次。

当Web项目启动时,做初始化工作,所以我们大部分是配置在Web.xml里面,这样项目一启动,就会执行相关的初始化工作,下面是Web.xml代码:

<servlet>
		<servlet-name>SpringMVCDispatcher</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>1</load-on-startup>
	</servlet>
	<servlet-mapping>
        <servlet-name>SpringMVCDispatcher</servlet-name>
        <url-pattern>*.jhtml</url-pattern>
    </servlet-mapping>
<servlet>
		<servlet-name>HessianDispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>
				classpath:hessian-service.xml
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
        <servlet-name>HessianDispatcher</servlet-name>
        <url-pattern>/service/*</url-pattern>
    </servlet-mapping>

这里配置了两个DispatcherServlet,后面会介绍到,怎么各自处理,有各自的上下文容器。

load-on-startup是启动的优先级,spring-mvc.xml是我们配置bean的一些信息

最早我们开始学习MVC结构时,就是学servlet,都是继 承了HttpServlet 类,也是重新了init、doGet、doPost、destroy方法,我这边就不介绍HttpServlet 类,DispatcherServlet也是间接最高继承了HttpServlet,如图所示:

我们先了解项目启动,DispatcherServlet和父类都做了什么事情呢?这是我们今天的重点。

第一步:DispatcherServlet继承了FrameworkServlet,FrameworkServlet继承了HttpServletBean,HttpServletBean继承了HttpServlet 类,而HttpServletBean类有一个入口点就是重写了init方法,如图所示:

init方法做了什么事情呢?接下来我们来具体分析:

init方法里有涉及到了BeanWrapper,PropertyValues,ResourceLoader。我这里大概介绍一下。

1)PropertyValues:获取Web.xml里面的servlet的init-param(web.xml)

  /**
		 * Create new ServletConfigPropertyValues.
		 * @param config ServletConfig we'll use to take PropertyValues from
		 * @param requiredProperties set of property names we need, where
		 * we can't accept default values
		 * @throws ServletException if any required properties are missing
		 */
		public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
			throws ServletException {
			Enumeration en = config.getInitParameterNames();
			while (en.hasMoreElements()) {
				String property = (String) en.nextElement();
				Object value = config.getInitParameter(property);
				addPropertyValue(new PropertyValue(property, value));
			}
		}

说明:

Enumeration en = config.getInitParameterNames();

获取了init-param的param-name和param-value值,并设置配置参数到PropertyValue,如图所示:

2)BeanWrapper:封装了bean的行为,提供了设置和获取属性值,它有对应的BeanWrapperImpl,如图所示:

3)ResourceLoader:接口仅有一个getResource(String location)的方法,可以根据一个资源地址加载文件资源。classpath:这种方式指定SpringMVC框架bean配置文件的来源。

ResourcePatternResolver扩展了ResourceLoader接口,获取资源

ResourcePatternResolver
resolver =new PathMatchingResourcePatternResolver();

resolver.getResources("classpath:spring-mvc.xml");

总结:

先通过PropertyValues获取web.xml文件init-param的参数值,然后通过ResourceLoader读取.xml配置信息,BeanWrapper对配置的标签进行解析和将系统默认的bean的各种属性设置到对应的bean属性。

在init方法里还调用了initServletBean();这里面又实现了什么。HttpServletBean在为子类提供模版、让子类根据自己的需求实现不同的ServletBean的初始化工作,这边是由HttpServletBean的子类FrameworkServlet来实现的,如图所示:

this.webApplicationContext = initWebApplicationContext();初始化SpringMVC 上下文容器,servlet的上下文容器是ServletContext。对initWebApplicationContext();进行跟踪,查看这个方法做了什么事情?

protected WebApplicationContext initWebApplicationContext() {
       //根节点上下文,是通过ContextLoaderListener加载的,服务器启动时,最先加载的
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		if (this.webApplicationContext != null) {
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                 //要对上下文设置父上下文和ID等
				if (!cwac.isActive()) {
  					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
         //Servlet不是由编程式注册到容器中,查找servletContext中已经注册的WebApplicationContext作为上下文
		if (wac == null) {
			wac = findWebApplicationContext();
		}
          //如果都没找到时,就用根上下文就创建一个上下文有ID
		if (wac == null) {
  			   wac = createWebApplicationContext(rootContext);
		}
        //在上下文关闭的情况下调用refesh可启动应用上下文,在已经启动的状态下,调用refresh则清除缓存并重新装载配置信息
		if (!this.refreshEventReceived) {
			onRefresh(wac);
		}
       //对不同的请求对应的DispatherServlet有不同的WebApplicationContext、并且都存放在ServletContext中
		if (this.publishContext) {
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

总结:

initWebApplicationContext初始化上下文,并作为值放到了ServletContext里,因为不同的DispatherServlet有对应的各自的上下文,而且上下文有设置父上下文和id属性等。上下文项目启动时会调用createWebApplicationContext()方法,如图所示:

然后会初始化,设置设置父上下文和id属性等,如图所示:

1)获取ContextLoaderListener加载的上下文并标示为跟上下文,如果是编程式传入,没初始化,以根节点为父上文,并设置ID等信息,然后初始化。

2)如果上下文是为空的,Servlet不是由编程式注册到容器中,查找servletContext中已经注册的WebApplicationContext作为上下文,如果都没找到时,就用根上下文就创建一个上下文有ID,在上下文关闭的情况下调用refesh可启动应用上下文,在已经启动的状态下,调用refresh则清除缓存并重新装载配置信息

3)对不同的请求对应的DispatherServlet有不同的WebApplicationContext、并且都存放在ServletContext中。以servlet-name为key保存在severtContext,前面有配置了两个DispatherServlet,都有各自的上下文容器,如图所示:

回调函数onRefresh还做了一些提供了SpringMVC各种编程元素的初始化工作, onRefresh在为子类提供模版、让子类根据自己的需求实现不同的onRefresh的初始化工作,这边是由FrameworkServlet的子类DispatcherServlet来实现的,如图所示:

我们现在来分析SpringMVC组件进行初始化,并封装到DispatcherServlet中

//初始化上传文件解析器

initMultipartResolver(context);

//初始化本地解析器

initLocaleResolver(context);

//初始化主题解析器

initThemeResolver(context);

//初始化映射处理器

initHandlerMappings(context);

//初始化适配器处理器

initHandlerAdapters(context);

//初始化异常处理器

initHandlerExceptionResolvers(context);

//初始化请求到视图名翻译器

initRequestToViewNameTranslator(context);

//初始化视图解析器

initViewResolvers(context);

我们这边拿几个比较主要的分析一下具体实现了什么。

第一:initHandlerMappings初始化映射处理器

 private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		if (this.detectAllHandlerMappings) {
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				OrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				}
		}
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

说明:

1)detectAllHandlerMappings默认是true,根据类型匹配机制查找上下文及父容器上下文中所有类型为HandlerMapping的bean,将它们作为该类型组件,并放到ArrayList<HandlerMapping>中。

2)detectAllHandlerMappings如果是false时,查找key为handlerMapping的HandlerMapping类型的bean为该类组件,而且 Collections.singletonList只有一个元素的集合。

3)List<HandlerMapping> 是为空的话,使用BeanNameUrlHandleMapping实现类创建该类的组件。

第二:initHandlerAdapters适配器处理器

private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;

		if (this.detectAllHandlerAdapters) {
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
				OrderComparator.sort(this.handlerAdapters);
			}
		}
		else {
			try {
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			}
			catch (NoSuchBeanDefinitionException ex) {
							}
		}
		if (this.handlerAdapters == null) {
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
			}
		}
	}

initHandlerAdapters适配器处理器初始化原理跟initHandlerMappings初始化映射处理器一样

这里就不在说明了。

总结:

1)initHandlerMapping会初始化了handlerMethods请求方法的映射,HandlerMapping是处理请求的映射的如图所示:

今天先讲SpringMVC的初始化,当DispatcherServlet初始化后,就会自动扫描上下文的bean,根据名称或者类型匹配的机制查找自定义的组件,找不到则使用DispatcherServlet。Properties定义默认的组件

总结:

HttpServletBean、FrameworkServlet、DispatcherServlet三个不同的类层次,SpringMVC对三个以抽象和继承来实现不用的功能,分工合作,实现了解耦的设计原则。

我们在回顾一下,各自做了什么事情,HttpServletBean是实现了获取web.xml中的<init-param>配置元素的值;FrameworkServlet实现了SpringMVC上下文并根据不同的DispatcherServlet放在以servlet-name为key值severtContext中;DispatcherServlet主要实现了初始化SpringMVC组件元素。

时间: 2024-08-26 16:06:56

SpringMVC源码深度分析DispatcherServlet核心的控制器(初始化)的相关文章

SpringMVC源代码深度分析DispatcherServlet核心的控制器(初始化)

SpringMVC是非常优秀的MVC框架,每一个框架都是为了我们提高开发效率,我们试图通过对SpringMVC的源码去了解这个框架,了解整个设计思想,框架要有扩展性,这里用的比較多是接口和抽象,是框架的主力,我们通过了解源码能对SpringMVC框架更了解,也能对我们开发思想有非常大的启示. SpringMVC由几个核心类和接口组成的.我们今天要的一个是DispatcherServlet核心的前置控制器.配置在Web.xml中.所以请求都经过它来统一分发的.SpringMVC几个核心类和接口都会

JAVA之ConcurrentHashMap源码深度分析

欢迎转载,请附上出处: http://blog.csdn.net/as02446418/article/details/47004573 我们首先来看一下ConcurrentHashMap类的声明: public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable 其中,这个类继承了java.util.AbstractMa

Spring5源码深度分析(二)之理解@Conditional,@Import注解

代码地址:https://github.com/showkawa/spring-annotation/tree/master/src/main/java/com/brian 1.源码分析二主要分析的内容 1.使用@Condition多条件注册bean对象[email protected]注解快速注入第三方bean对象[email protected] 开启原理4.基于ImportBeanDefinitionRegistrar注册bean5.基于FactoryBean注册bean对象 1.使用@C

spring源码深度解析-1核心实现

佟濒嬖 输榨 垤阈霎俎 瘁怛午瓷 翱&距跺 思疽愠 位儿属 缓洵ニ糜 柜调渤黟 象兕盅刿 俘¢筋芹 饭幌︸槛 娜商愫生 戕铁呤鲚 拉茴胬堍 抚┲耵科 ╄谜砼鼷 唿滋择唰 办公室内拉着窗帘天花板上的吊灯散着柔和的光芒陈琳斜 驵词辈钭 澶I谡 偃湖邾▲ 蓰靳╂Д 菟洫酽鳏 郇哟憷缏 都升不了花了近三个小时的时间终 卑跑谆牌 H刀 肽熨幻岛 僻苛乙思 牵恳性 夥驱ず 洪淡 怂屐速 拼③鄯审 虱蠊耋腮 荀杈κ 厅莉彩 起启滕 壶姗讲铋 蔷檫 庇频踉啕 但不理她径自下楼到

SpringMVC源码分析(4)剖析DispatcherServlet重要组件

<SpringMVC源码分析(3)DispatcherServlet的请求处理流程 >简单介绍了一个请求的处理过程, 简略描述了调用过程,并没有涉及过多细节,如url匹配,报文解析转换等. <SpringMVC源码分析(2)DispatcherServlet的初始化>:介绍了servlet的初始化过程,尤其initStrategies方法. 本文主要总结DispatcherServlet几个重要组件的关系. 1.类图 该类图并没有全面的描述SpringMVC相关类,重点说明组件的关

SpringMVC源码分析(3)DispatcherServlet的请求处理流程

<SpringMVC源码分析(1)标签解析>:介绍了解析过程中,初始化若干组件. <SpringMVC源码分析(2)DispatcherServlet的初始化>:初始化DispatcherServlet的多个组件. 本文继续分析DispatcherServlet解析请求的过程. 概览 ①:DispatcherServlet是springmvc中的前端控制器(front controller),负责接收request并将request转发给对应的处理组件. ②:HanlerMappi

SpringMVC源码分析(2)DispatcherServlet的初始化

DispatcherServlet的初始化,是在org.springframework.web.context.ContextLoaderListener完成加载后,才开始的.这时候WebApplicationContext(包含DAO,Service等)已经初始完毕. DispatcherServlet的初始过程主要完成 1.WebApplicationContext父子容器维护 2.初始化Servlet策略 本文主要内容 DispatcherServlet的集成体系 DispatcherSe

【转】springmvc源码分析链接

SpringMVC源码 SpringMVC源码分析系列 说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而 springmvc是依托着Servlet容器元素来设计的,同时springmvc基于Spring框架,Spring框架想必搞java的同学都很熟 悉. 一进Spring的官网就发现了这样一排醒目的文字, spring可以让我们构造简单的.便携的.又快又易于扩展的基于jvm的系统和应用程序. 没错,基于Spr

SpringMVC源码分析系列

说到java的mvc框架,struts2和springmvc想必大家都知道,struts2的设计基本上完全脱离了Servlet容器,而springmvc是依托着Servlet容器元素来设计的,同时springmvc基于Spring框架,Spring框架想必搞java的同学都很熟悉. 一进Spring的官网就发现了这样一排醒目的文字, spring可以让我们构造简单的.便携的.又快又易于扩展的基于jvm的系统和应用程序. 没错,基于Spring的MVC框架SpringMVC同样也可以构造具有这些特