SpringMVC之DispatcherServlet(一)

上一篇介绍了ContextLoaderListener,作用就是创建 根上下文 XmlWebApplicationContext , 并储存在全局上下文 ServletContext 中。

在 web.xml 中除了 ContextLoaderListener 配置之外,还有对 DispatcherServlet 的配置。作为一个Servlet,所有的 Web 请求都需要通过它来处理,进行转发,匹配,数据处理后,并转由页面进行展现,它可以说是 SpringMVC 最核心的部分。除此之外 SpringMVC 还有不同的 HandlerMapping 映射策略,各种 Controller 控制器的实现,各种视图解析,拦截器,LocalResolver 国际化处理。

在完成对 ContextLoaderListener 的初始化后,Web 容器开始初始化 DispatcherServlet。 DispatcherServlet 会建立自己的上下文来持有SpringMVC的Bean 对象,过程是首先从 ServletContext 中获 取上下文作为自己的父上下文,再对自己的上下文进行初始化,最后存储到 ServletContext 中。

来看看继承图:
在这里插入图片描述

DispatcherServlet 继承自 HttpServlet ,通过使用 Servlet API 对 HTTP 请求进行响应。其工作大致分为两个部分:一是初始化部分,由 init() 启动,经 initServletBean() , 通过 initWebApplicationContext() 最终调用 DispatcherServlet 的 initStrategies 方法,在该方法里对诸如 handlerMapping,ViewResolver 等进行初始化;另一个是对 HTTP 请求进行响应的部分,作为一个 Servlet,Web 容器会调用其 doGet(),doPost 等方法,经过 FrameworkServlet 的processRequest() 简单处理后会调用 DispatcherServlet 的doService() 方法,该方法会调用 doDispatch(),doDispatch 是实现 MVC 模式的主要部分,流程图如下
在这里插入图片描述
关于 Servlet
Servlet是一个Java编写的程序,基于Http协议,在服务端运行,主要处理客户端的请求并将结果发送给客户端。其生命周期由Servlet容器来控制,分为三个阶段:初始化,运行和销毁。
初始化阶段servlet容器会做以下动作:

  • 加载servlet类,把servlet.class文件中的数据读到内存中
  • 创建一个ServletConfig对象,该对象包含了servlet的初始化配置信息
  • 创建一个servlet对象
  • 调用servlet对象的init方法进行初始化

运行阶段:当请求到来servlet容器会针对该请求创建ServletRequest和ServletResponse对象,然后调用service方法,并将这两个作为参数传给service方法。该方法通过ServletRequest对象获得请求信息并处理,再通过ServletResponse对象生成这个请求的响应结果,然后销毁这两个对象。
销毁阶段:当Web应用被终止,servlet容器会先调用servlet对象的destroy方法,然后再销毁servlet对象,包括与其相关联的servletConfig对象。可以在destroy方法中释放servlet所占用的资源,如关闭数据库连接,关闭文件输入输出流等。

servlet的框架由两个Java包组成:javax.servletjavax.servlet.http。javax.servlet中定义了所有的servlet类必须实现或扩展的通用接口和类,javax.servlet.http中定义了采用HTTP通信协议的HttpServlet类。

servlet被设计成请求驱动,HTTP请求方式包括delete, get, options, post, put, trace,在HttpServlet类中分别提供了相应的服务方法,doGet(), doDelete() …

DispatcherServlet的初始化

正如上面所说servlet初始化阶段会调用其init方法,来看看DispatcherServlet的init方法。
定位到HttpServletBean#init()

	@Override
	public final void init() throws ServletException {

		// 获取该<servlet>中的<init-param>中设置的键值对,封装在PropertyValues中
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				//将当前这个servlet类转化为BeanWrapper,从而能够以Spring的方式对init-param 中的属性进行注入
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				//注册自定义属性编辑器,对Resource类型的属性会使用ResourceEditor进行解析
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				// 空实现
				initBeanWrapper(bw);
				//属性注入
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// 空实现
		initServletBean();
	}

DispatcherServlet的初始化过程主要是将servlet实例转化为BeanWrapper类型,以便利用Spring的注入功能进行属性注入。如namespace,contextConfigLocation,contextAttribute,contextClass等,这些你都可以在web.xml中以初始化参数的方式配置在<servlet>下声明配置,这些参数在FrameworkServlet,Spring将这些参数注入到对应的值中。
1,封装及验证初始化参数

		public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
				throws ServletException {

			Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
					new HashSet<>(requiredProperties) : null);

			Enumeration<String> paramNames = config.getInitParameterNames();
			while (paramNames.hasMoreElements()) {
				String property = paramNames.nextElement();
				Object value = config.getInitParameter(property);
				addPropertyValue(new PropertyValue(property, value));
				if (missingProps != null) {
					missingProps.remove(property);
				}
			}

			// Fail if we are still missing properties.
			if (!CollectionUtils.isEmpty(missingProps)) {
				throw new ServletException(
						"Initialization from ServletConfig for servlet '" + config.getServletName() +
						"' failed; the following required properties were missing: " +
						StringUtils.collectionToDelimitedString(missingProps, ", "));
			}
		}
	}

对初始化的参数进行封装及验证,指的是将<servlet>中的<init-param>中的配置封装成PropertyValue,再存储到private final List<PropertyValue> propertyValueList;requiredProperties代表你所需的属性,若是<init-param>没有设置这些属性则会报错,HttpServletBean的子类可以通过addRequiredProperty方法指定requiredProperties

protected final void addRequiredProperty(String property) {
		this.requiredProperties.add(property);

2,将当前servlet转化为BeanWrapper
PropertyAccessorFactory.forBeanPropertyAccess是Spring的工具方法,用于将指定实例转化为Spring中可以处理的BeanWrapper类型的实例。
3,Resource属性编辑器
对属性进行注入过程中一旦遇到 Resource 类型的属性就会使用ResourceEditor 去解析。

DispatcherServlet持有的IOC容器的初始化

ContextLoaderListener加载的时候已经创建了 WebApplicationContext 作为 根上下文,在 initServletBean 中创建自己的上下文,并以根上下文为父。可以认为根上下文是与 Web 应用相对应的一个上下文,而 DispatcherServlet 持有的上下文是和 Servlet 对应的一个上下文。在一个 Web 应用中,可以容纳多个 Servlet 存在,根上下文是它们共同的父上下文。
这IOC容器 父 与 子 的关系是:当向 IOC 容器 getBean 时,会先向其 父 去获取,也就是说 根上下文 定义的 bean 是被各个 Servlet 共享的。

DispatcherServlet 持有的上下文初始化后会设置到 Web 容器的上下文中,即 ServletContext,存储的 key 即名称就是在 web.xml 中设置的 <servlet-name>

来看看 FrameworkServlet 中的实现

	@Override
	protected final void initServletBean() throws ServletException {
		......
		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		......
	}

下面从initWebApplicationContext开始介绍WebApplicationContext的初始化。

WebApplicationContext的初始化

initWebApplicationContext 就是创建/更新 WebApplicationContext 实例并对 servlet 功能所使用的变量进行初始化。

	protected WebApplicationContext initWebApplicationContext() {
		// 根上下文,即ContextLoaderListener初始化的XmlWebApplicationContext
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
		
		// 本 Servlet 的上下文已创建
		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// 未指定父上下文
					if (cwac.getParent() == null) {
						//指定根上下文为其父,构建双亲上下文
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		// 该Servlet的上下文未构建,这里是去找寻该上下文
		//所谓找寻,指的是去Web容器上下文中(ServletContext)通过你在<servlet>下<init-param>中设置的contextAttribute属性的值
		//来找寻你指定的上下文 WebApplicationContext
		if (wac == null) {
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}
		// refreshEventReceived标识onRefresh是否已被调用
		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

1,寻找与创建上下文WebApplicationContext

ContextLoaderListener会创建XmlWebApplicationConntext实例并将其存储进ServletContext中,key为WebApplicationContext.class.getName() + ".ROOT";,上面 rootContext 指的就是它。
this.webApplicationContext == null 说明在构建时没有注入,调用findWebApplicationContext方法,该方法是去ServletContext中获取你指定的WebApplicationContext,根据你设置的contextAttribute的值

	protected WebApplicationContext findWebApplicationContext() {
		// 获取<servlet>下<init-param>中设置的contextAttribute属性的值
		// 若没有设置则返回null
		String attrName = getContextAttribute();
		if (attrName == null) {
			return null;
		}
		// 这里就是去全局上下文servletContext中去找你指定的WebApplicationContext
		// 没有则抛异常
		WebApplicationContext wac =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
		if (wac == null) {
			throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
		}
		return wac;
	}

findWebApplicationContext返回null,则createWebApplicationContext创建

	protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
		return createWebApplicationContext((ApplicationContext) parent);
	}

这里parent指的是ContextLoaderListener创建的XmlWebApplicationContext 根上下文

	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
	//获取servlet的初始化参数contextClass,如果没有设置则默认为XmlWebApplicationContext
		Class<?> contextClass = getContextClass();
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
		// 反射实例化实例
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
		//设置父上下文
		wac.setParent(parent);
		//获取该上下文的配置文件
		//处理后赋给AbstractRefreshableConfigApplicationContext类的String[] configLocations字段
		//该类是 WebApplicationContext的父级类
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

getContextClass

	public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
	
	private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
	
	public Class<?> getContextClass() {
		return this.contextClass;
	}

可以看出默认是 XmlWebApplicationContext ,若你在 web.xml 中该<servlet>下<init-param>中配置了 contextClass 的值,则在上面一开始的 init 方法里你设置的值就会被注入到这里,从而获得的是你设置的值。

2,刷新onRefresh

onRefresh 是 FrameworkServlet 类中提供的空方法,在其子类DispatcherServlet中实现,主要是初始化MVC各个部分。

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}
	// 初始化MultipartResolver,LocaleResolver......
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		//初始化LocaleResolver,与国际化配置相关
		initLocaleResolver(context);
		//初始化ThemeResolver,主题解析器的接口
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

初始化MultipartResolver
MultipartResolver 用于处理文件上传,需要添加multipart解析器,如下
在DispatcherServlet的配置文件中指定

    <!-- 文件上传,id名称固定 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="10485760"/> <!-- 文件最大size限制为10m -->
        <property name="maxInMemorySize" value="4096" /> <!-- 文件上传过程中使用的最大内存块 -->
        <property name="defaultEncoding" value="UTF-8"></property>
    </bean>

initMultipartResolver 就是获取 CommonsMultipartResolver 的 bean 赋给 DispatcherServlet#multipartResolver

	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;
			......
			}
		}
	}

初始化HandlerMappings
DispatcherServlet 按优先级的高低来选择 HandlerMapping ,如果当前的 HandlerMapping 能够返回可用的handler,则使用当前返回的 handler 来处理请求。

	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		// 默认为true,从所有IOC容器中去获取
		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else { // 根据名称从当前的IOC容器中通过getBean获取handlerMapping
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}
		// 仍没有找到的化需要为servlet生成默认的handlerMappings,
		// 默认值设置在 DispatcherServlet.properties
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			......
			}
		}
	}

关于 detectAllHandlerMappings

<servlet>
	<init-param>
		<param-name>detectAllHandlerMappings</init-param>
		<param-value>false</param-value>
	</init-param>

若是你有如上设置,则 SpringMVC 将查找名为 “handlerMapping” 的bean,并作为唯一的handlerMapping。否则会加载该WebApplicationContext下的所有handlerMapping。若是没有则调用 getDefaultStrategies 创建。

	protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		String key = strategyInterface.getName();
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			List<T> strategies = new ArrayList<>(classNames.length);
			for (String className : classNames) {
				try {
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
					Object strategy = createDefaultStrategy(context, clazz);
					strategies.add((T) strategy);
				}
				......
			}
			return strategies;
		}
		else {
			return new LinkedList<>();
		}
	}

获取配置文件中设置的 HandlerMapping,关于 defaultStrategies

	private static final Properties defaultStrategies;

	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		......
	}

文件名为 "DispatcherServlet.properties",来看看该配置文件指定的 HandlerMapping
org.springframework.web.servlet.DispatcherServlet所在目录下的 DispatcherServlet.properties 中定义的 HandlerMapping

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

初始化HandlerAdapters
代码逻辑与上面一样,同样可以设置 detectAllHandlerAdapters 值为 false, 来加载名为 handlerAdapter 的 HandlerAdapter 的 bean, 用户可以通过此来加载自定义的 HandlerAdapter 。来看看 Spring 配置文件DispatcherServlet.properties 中指定的 HandlerAdapter。

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值