Spring 之 MVC 详解

概念

什么是MVC?

​ MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。就是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。

  • Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
  • View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。
  • Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作。

Spring MVC

Spring Web MVC是构建在Servlet API上的原始Web框架,从一开始就包含在Spring Framework中。 正式名称 “Spring Web MVC,” 来自其源模块名称,但它通常被称为“Spring MVC”。

Spring MVC 是一个模型 - 视图 - 控制器(MVC)的Web框架建立在中央前端控制器servlet(DispatcherServlet),它负责发送每个请求到合适的处理程序,使用视图来最终返回响应结果的概念。Spring MVC 是 Spring 产品组合的一部分,它享有 Spring IoC容器紧密结合Spring松耦合等特点,因此它有Spring的所有优点。

流程

Spring 启动流程

  1. 对于一个Web应用,其部署在Web容器中,Web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的Spring IoC容器提供宿主环境;
  2. 在web.xml中会提供有contextLoaderListener。在Web容器启动时,会触发容器初始化事件,此时ContextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,Spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是Spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,Spring以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
  3. ContextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个Servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有Spring MVC相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是XmlWebApplicationContext。初始化完毕后,Spring以与Servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个Servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个Servlet共享相同的Bean,即根上下文(第2步中初始化的上下文)定义的那些Bean。

Spring MVC 执行流程

​ 当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。
在这里插入图片描述

  1. DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求
  2. 通过事先解析好的HandlerMapping(处理器映射),获取请求Url对应的Handler(处理器)
  3. 返回由处理器和拦截器组成的HandlerExecutionChain(执行链),运行Hadler需要有对应的环境通过HandlerAdapter(处理器适配器)按照特定的规则去执行Handler
  4. HandlerExecutionChain执行完成后会返回ModelAndView(模型和试图)给DispatcherServlet
  5. DispatcherServlet调用视图ViewResolver(解析器)来解析ModelAndView
  6. DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图,进行试图渲染
  7. 响应用户请求,返回渲染后的视图

Servlet

生命周期

Servlet的生命周期包含了下面4个阶段:

  1. 加载和实例化:当web.xml中配置的Servlet进行加载或容器检测到需要这个Servlet来响应第一个请求时,创建这个Servlet实例。Servlet容器需要知道Servlet的位置才能加载,并且容器是通过默认的无参构造方法反射来进行Servlet实例化。
  2. 初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象。初始化的目的是为了让Servlet对象在处理客户端请求前完成一些初始化的工作,如建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只被调用一次。在初始化期间,Servlet实例可以使用容器为它准备的ServletConfig对象从Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息。
  3. 处理请求:Servlet容器调用Servlet的service()方法对请求进行处理。要注意的是,在service()方法调用之前,init()方法必须成功执行。在service()方法中,Servlet实例通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。
  4. 服务终止:当容器检测到一个Servlet实例应该从服务中被移除的时候,容器就会调用Servlet实例的destroy()方法,以便让该实例可以释放它所使用的资源,保存数据到持久存储设备中。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。如果再次需要这个Servlet处理请求,Servlet容器会创建一个新的Servlet实例。

线程安全

当服务器接收到来自客户端的多个请求时,Servlet容器通过调度线程(Dispatchaer Thread) 调度它管理下线程池中等待执行的线程(Worker Thread)给请求者执行Servlet实例的service()方法。此时会有多个线程同时执行同一个Servlet实例的service()方法,因此必须考虑线程安全的问题。

虽然service()方法运行在多线程的环境下,并不一定要同步该方法。而是要看这个方法在执行过程中访问的资源类型及对资源的访问方式。分析如下:

  • 如果service()方法没有访问Servlet的成员变量也没有访问全局的资源比如静态变量、文件、数据库连接等,而是只使用了当前线程自己的资源,比如非指向全局资源的临时变量、request和response对象等。该方法本身就是线程安全的,不必进行任何的同步控制。
  • 如果service()方法访问了Servlet的成员变量,但是对该变量的操作是只读操作,该方法本身就是线程安全的,不必进行任何的同步控制。
  • 如果service()方法访问了Servlet的成员变量,并且对该变量的操作既有读又有写,通常需要加上同步控制语句。
  • 如果service()方法访问了全局的静态变量,并且同一时刻系统中也可能有其它线程访问该静态变量,都有读写操作,则通常需要加上同步控制语句。
  • 如果service()方法访问了全局的资源,比如文件、数据库连接等,通常需要加上同步控制语句。

原理

相信大家通过上面的讲解已经大致了解Spring MVC及其执行流程,接下来我们通过源码来看具体是如何实现Servlet初始化和处理请求的。

Servlet 初始化

DispatcherServlet 的 init 方法开始找起,在父类 HttpServletBean 找到了对应的 init 方法。这里主要是定位资源并加载配置信息,具体的实现在 initServletBean 方法中。

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) {
			if (logger.isErrorEnabled()) {
				logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			}
			throw ex;
		}
	}

	//初始化子Servlet信息
	initServletBean();
}

跳转到 FrameworkServlet.initServletBean 方法,进行当前 Servlet 的创建。

protected final void initServletBean() throws ServletException {
	getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
	if (logger.isInfoEnabled()) {
		logger.info("Initializing Servlet '" + getServletName() + "'");
	}
	long startTime = System.currentTimeMillis();

	try {
		// webApplicationContext是FrameworkServlet的上下文,后续的方法是进行上下万的初始化
		this.webApplicationContext = initWebApplicationContext();
		//自定义实现需要进行初始化的内容
		initFrameworkServlet();
	}
	catch (ServletException | RuntimeException ex) {
		logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (logger.isDebugEnabled()) {
		String value = this.enableLoggingRequestDetails ?
				"shown which may lead to unsafe logging of potentially sensitive data" :
				"masked to prevent unsafe logging of potentially sensitive data";
		logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
				"': request parameters and headers will be " + value);
	}

	if (logger.isInfoEnabled()) {
		logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
	}
}

继续跟进 initWebApplicationContext 方法。这里需要注意当设置了 load-on-startup 时,DispatcherServlet 会在启动时进行初始化。否则会在第一次接收到请求的时候才进行初始化,但是 WebApplicationContext 是一定在启动时初始化完成的。

  • 如果是在启动时就进行 DispatcherServlet 初始化,则会进入 configureAndRefreshWebApplicationContext 方法执行 IOC 容器初始化流程。在 IOC 容器初始化完成后,FrameworkServlet 通过监听 IOC 容器初始化完成事件继续完成 DispatcherServlet 初始化操作。
  • 如果在接收到第一次请求时进行 DispatcherServlet 初始化,因为 WebApplicationContext 已经在启动时初始化完成了。并且当前 Servlet 上下文与根上下文是同一个 WebApplicationContext,所以当前Servlet不需要再次进行 IoC 容器初始化。
protected WebApplicationContext initWebApplicationContext() {
	//先从ServletContext中获得父容器WebApplicationContext
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	//声明子容器
	WebApplicationContext wac = null;

	//建立父、子容器之间的关联关系
	if (this.webApplicationContext != null) {
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			//判断子容器是否处于活动状态
			if (!cwac.isActive()) {
				if (cwac.getParent() == null) {
					cwac.setParent(rootContext);
				}
				//这个方法里面进行配置信息并调用了AbstractApplication的refresh()方法
				//模板方法,规定IOC初始化基本流程
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	if (wac == null) {
		wac = findWebApplicationContext();
	}
	if (wac == null) {
		
		wac = createWebApplicationContext(rootContext);
	}

	//判断onRefresh方法是否调用过
	if (!this.refreshEventReceived) {
		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;
}

我们来大概看一下是如何在启动时就执行初始化的,跟进 createWebApplicationContext 方法。主要就是进行初始参数和环境配置,最后调用 AbstractApplicationContext.refresh() 方法。

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

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
	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);
	String configLocation = getContextConfigLocation();
	if (configLocation != null) {
		wac.setConfigLocation(configLocation);
	}
	configureAndRefreshWebApplicationContext(wac);

	return wac;
}
	
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		if (this.contextId != null) {
			wac.setId(this.contextId);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
		}
	}

	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);
	applyInitializers(wac);
	//刷新并初始化容器
	wac.refresh();
}

就不再对上下文初始化再次进行分析,不懂的可以看我之前的文章 Spring 之 IOC 详解(基于注解方式)

主要看 refresh 方法调用的 finishRefresh 方法,初始化容器的生命周期事件处理器,并发布容器的生命周期事件。

protected void finishRefresh() {
	// 清除上下文级别的资源缓存(例如来自扫描的ASM元数据)。
	clearResourceCaches();

	// 为此上下文初始化生命周期处理器。
	initLifecycleProcessor();

	// 首先将刷新传播到生命周期处理器。
	getLifecycleProcessor().onRefresh();

	// 发布最终事件。
	publishEvent(new ContextRefreshedEvent(this));

	// Participate in LiveBeansView MBean, if active.
	LiveBeansView.registerApplicationContext(this);
}

在发布最终事件后,FrameworkServlet.onApplicationEvent() 方法就会接收到该事件继续完成 DispatcherServlet 的初始化流程。

public void onApplicationEvent(ContextRefreshedEvent event) {
	this.refreshEventReceived = true;
	synchronized (this.onRefreshMonitor) {
		onRefresh(event.getApplicationContext());
	}
}

所以不论是何时完成 DispatcherServlet 的初始化工作,都会走 onRefresh 方法。继续跟进 onRefresh 方法,具体实现回到了 DispatcherServlet 类中。主要是初始化 Spring MVC 的九大组件,这里先讲下九大组件的功能:

  1. MultipartResolver(文件处理器),对应的初始化方法是initMultipartResolver(context),用于处理文件上传请求。处理方法是将普通的Request包装成MultipartHttpServletRequest来实现,MultipartHttpServletRequest可以直接调用getFile方法直接获取文件。如果是多个文件上传可以通过调用getFileMap得到Map<FileName, File>结构。
  2. LocaleResolver(当前环境处理器),对应的初始化方法是initLocaleResolver(context),用于当前Web容器的语言,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
  3. ThemeResolver(主题处理器),对应的初始化方法是initThemeResolver(context),用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源,如图片、css样式等。SpringMVC的主题也支持国际化。
  4. HandlerMappings(处理器映射器),对应的初始化方法是initHandlerMappings(context),这就是根据用户请求的资源URI来查找Handler和Interceptors的。在SpringMVC中会有很多请求,每个请求都需要一个Handler来进行实际的请求处理。
  5. HandlerAdapters(处理器适配器),对应的初始化方法是initHandlerAdapters(context),它就是一个适配器。因为Spring MVC中的Handler可以是任意形式的,只要能够处理请求即可。而Servlet处理方法的结构却是固定的,都是doService(HttpServletRequest req, HttpServletResponse resp)方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理,这就是HandlerAdapters要做的事情。
  6. HandlerExceptionResolvers(异常处理器),对应的初始化方法是initHandlerExceptionResolvers(context),用来处理Handler处理过程中产生的异常情况的处理器。根据异常设置ModelAndView,之后由 render 方法渲染成页面。在SpringMVC中HandlerExceptionResolver只是处理在Handler处理阶段产生的异常,渲染阶段的异常不归它管。这也是Spring MVC 组件的一大原则分工明确、互不干涉。
  7. RequestToViewNameTranslator(视图名称翻译器),对应的初始化方法是initRequestToViewNameTranslator(context),从Request获取ViewName。因为 ViewResolver是根据ViewName来查找View的,而有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要这个组件从Request获取ViewName了。
  8. ViewResolvers(视图解析器),对应的初始化方法是initViewResolvers(context),用于找到渲染使用的模板并进行参数填入。ViewResolver用来将String类型的视图名和Locale解析为View类型的视图,View是用来渲染页面的。也就是将程序返回的参数和数据填入模板里,生成HTML(也可能是其它类型)文件。默认情况下Spring MVC会为我们自动配置一个 InternalResourceViewResolver,针对JSP类型视图。
  9. FlashMapManager(参数传递管理器),对应的初始化方法是initFlashMapManager(context),用于Redirect(重定向)时的参数数据传递。可以将需要传递的数据写入Request的属性OUTPUT_FLASH_MAP_ATTRIBUTE中,这样在Redirect后的Handler处理器上可以直接通过Model获取数据。而FlashMapManager就是用来管理FlashMap。
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

/**
 * 初始化此servlet使用的策略对象。
 */
protected void initStrategies(ApplicationContext context) {
	//多文件上传的组件
	initMultipartResolver(context);
	//初始化本地语言环境
	initLocaleResolver(context);
	//初始化模板处理器
	initThemeResolver(context);
	//初始化处理器集合
	initHandlerMappings(context);
	//初始化参数适配器
	initHandlerAdapters(context);
	//初始化异常拦截器
	initHandlerExceptionResolvers(context);
	//初始化视图解析器
	initRequestToViewNameTranslator(context);
	//初始化视图转换器
	initViewResolvers(context);
	//初始化参数缓冲器
	initFlashMapManager(context);
}

九大组件的初始化流程大致相同,在这里我们看最为核心的 HandlerMapping 组件。

Spring MVC 进行DispatcherServlet初始化时,默认会加载当前系统中所有实现了 HandlerMapping 接口的 Bean 对象。如果只希望 Spring MVC 只加载指定的 HandlerMapping,可以修改 web.xml 中的DispatcherServlet 的初始化参数,将 detectAllHandlerMappings 的值设置为 false。这样 Spring MVC 就只会查找名为 “handlerMapping” 的bean,并作为当前系统的唯一的HandlerMapping。

private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;
	// 是否查找所有HandlerMapping标识
	if (this.detectAllHandlerMappings) {
		// 在ApplicationContext中找到所有HandlerMappings,包括父上下文。
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<>(matchingBeans.values());
			//按照HandlerMapping的优先级进行排序,根据URL进行匹配查找Handler时按照此顺序轮询
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		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.
		}
	}
	// 确保至少有一个HandlerMapping,如果没有找到,使用默认策略,注册一个默认的
	if (this.handlerMappings == null) {
		//按照DispatcherServlet.properties所定义的内容加载默认的HandlerMapping
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}
}

HandlerMapping 初始化

接下来我们先看下 HandlerMapping 对应的 UML 类图关系
在这里插入图片描述
我们常用的方式通过 @RequestMapping 注解定义 URL 路径,由于 RequestMappingHandlerMapping 类初始化流程较为繁琐。这里就先不分析,后续有空单独写一篇文章进行解析。

Spring MVC 默认是通过 URL 与 BeanName 进行映射,直接根据 URL 进行匹配获取对应的 Handler。我们就来看看实现类 BeanNameUrlHandlerMapping 是如何初始化映射关系的,寻找 BeanNameUrlHandlerMapping 的 initApplicationContext 方法。实际实现是在其父类 AbstractDetectingUrlHandlerMapping 中,源码如下:

public void initApplicationContext() throws ApplicationContextException {
	//初始化拦截器
	super.initApplicationContext();
	//建立当前上下文中所有URL与BeanName的映射关系
	detectHandlers();
}
protected void detectHandlers() throws BeansException {
	ApplicationContext applicationContext = obtainApplicationContext();
	//获取ApplicationContext容器中所有bean的Name
	String[] beanNames = (this.detectHandlersInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
			applicationContext.getBeanNamesForType(Object.class));

	//遍历beanNames,并找到这些bean对应的url
	for (String beanName : beanNames) {
		//找bean上的所有url(Controller上的url+方法上的url),该方法由对应的子类实现
		String[] urls = determineUrlsForHandler(beanName);
		if (!ObjectUtils.isEmpty(urls)) {
			// 保存urls和beanName的对应关系,该方法在父类AbstractUrlHandlerMapping中实现
			registerHandler(urls, beanName);
		}
	}

	if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
		logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
	}
}

继续看 BeanNameUrlHandlerMapping.determineUrlsForHandler 方法是如何查 beanName 上所有映射的 URL。

protected String[] determineUrlsForHandler(String beanName) {
	List<String> urls = new ArrayList<>();
	if (beanName.startsWith("/")) {
		urls.add(beanName);
	}
	String[] aliases = obtainApplicationContext().getAliases(beanName);
	for (String alias : aliases) {
		if (alias.startsWith("/")) {
			urls.add(alias);
		}
	}
	return StringUtils.toStringArray(urls);
}

到这里 HandlerMapping 组件就已经建立所有 URL 和 BeanName 的对应关系。

处理请求

Java EE 中的 Servlet 规范当发送请求的时候每次都是由 Servlet 的 doget 或者 dopost 方法进行接收,但是你在 DispatcherServlet 对应的方法实现在 FrameworkServlet 中。

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	processRequest(request, response);
}

可以看到无论什么请求都会调用 processRequest 方法,我们继续跟进。主要完成处理请求和发布事件,源码如下:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
	long startTime = System.currentTimeMillis();
	Throwable failureCause = null;
	// 获取之前设置的LocaleContext上下文
	LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
	// 以当前的request作用域来创建一个上下文对象
	LocaleContext localeContext = buildLocaleContext(request);

	// 获取之前的request属性值,如果是转发就有
	RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
	// previousAttributes若为null,那么就new ServletRequestAttributes(request, response);如果不等于空,就直接返回
	ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

	// 获取异步管理器
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

	//将之前设置request和locale上下文绑定到requestContext中
	initContextHolders(request, localeContext, requestAttributes);

	try {
		//在子类中进行逻辑实现
		doService(request, response);
	}
	catch (ServletException | IOException ex) {
		failureCause = ex;
		throw ex;
	}
	catch (Throwable ex) {
		failureCause = ex;
		throw new NestedServletException("Request processing failed", ex);
	}

	finally {
		//设置当前LocaleContext上下文和Request属性,用于转发后获取
		resetContextHolders(request, previousLocaleContext, previousAttributes);
		if (requestAttributes != null) {
			requestAttributes.requestCompleted();
		}
		logResult(request, response, failureCause, asyncManager);
		//发布请求处理完成事件
		publishRequestHandledEvent(request, response, startTime, failureCause);
	}
}

继续跟进就回到了 DispatcherServlet.doService 方法中,主要完成一些数据备份及设置。然后调用 doDispatch 方法进行处理请求,源码如下:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	logRequest(request);

	// 如果是include请求(请求包含) 那么就把request域中的数据保存一份快照版本
	// 等doDispatch结束之后,会把这个快照版本的数据覆盖到新的request里面去
	Map<String, Object> attributesSnapshot = null;
	if (WebUtils.isIncludeRequest(request)) {
		attributesSnapshot = new HashMap<>();
		Enumeration<?> attrNames = request.getAttributeNames();
		while (attrNames.hasMoreElements()) {
			String attrName = (String) attrNames.nextElement();
			if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
				attributesSnapshot.put(attrName, request.getAttribute(attrName));
			}
		}
	}

	// 把一些常用对象放进请求域  方便Handler里面可以随意获取
	request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
	request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
	request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
	request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

	// 从flashMapManager中获取重定向的相关参数
	if (this.flashMapManager != null) {
		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
		}
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
	}

	try {
		//DispatcherServlet中最重要的方法,由此方法来分发请求,进行处理
		doDispatch(request, response);
	}
	finally {
		if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			if (attributesSnapshot != null) {
				restoreAttributesAfterInclude(request, attributesSnapshot);
			}
		}
	}
}

核心处理逻辑都在 doDispatch 方法中,源码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// 定义一个已处理请求,指向参数的request
	HttpServletRequest processedRequest = request;
	// 定义处理器执行连,内部封装拦截器列表和处理器
	HandlerExecutionChain mappedHandler = null;
	// 是否有文件上传的请求标志
	boolean multipartRequestParsed = false;

	// 获取异步管理器,执行异步操作
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	try {
		// 保存处理器执行的返回结果
		ModelAndView mv = null;
		// 保存处理过程中的异常
		Exception dispatchException = null;

		try {
			// 判断当前请求是否是上传请求,如果是则将request包装成StandardMultipartHttpServletRequest后返回
			processedRequest = checkMultipart(request);
			// 判断当前请求是否是文件上传的请求,如果是则说明是上传请求已经处理
			multipartRequestParsed = (processedRequest != request);

			// Determine handler for the current request.
			// 获取可处理当前请求的请求处理器,通过HandlerMapping进行查找
			mappedHandler = getHandler(processedRequest);
			// 如果没有,就执行没有处理器的逻辑
			if (mappedHandler == null) {
				// 在内部处理中抛出异常或者返回404
				noHandlerFound(processedRequest, response);
				return;
			}

			// 根据当前请求的处理器获取支持该处理器的适配器
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			// 处理last-modified请求头,用于判断请求内容是否发生修改
			String method = request.getMethod();
			boolean isGet = "GET".equals(method);
			// 处理last-modified请求头,用于判断请求内容是否发生修改
			if (isGet || "HEAD".equals(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}

			// 通过mappedHandler这个HandlerExecutionChain执行链的封装,链式执行所有连接器的前置拦截方法
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				// 任意一个拦截器的前置拦截方法返回false,提前结束请求的处理
				return;
			}

			// 执行处理适配器的处理方法,传入请求,对请求进行处理,此方法的返回值是ModelAndView对象,封装了模型和视图
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			// 如果是异步处理,则直接返回,后续处理通过异步执行
			if (asyncManager.isConcurrentHandlingStarted()) {
				return;
			}

			// 返回的mv对象中如果没有视图名称,则根据请求设置默认视图名
			applyDefaultViewName(processedRequest, mv);
			// 请求处理正常完成,链式执行所有拦截器的后置方法
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
			// 保存异常信息
			dispatchException = ex;
		}
		catch (Throwable err) {
			// 4.3版本之后提供了error类型异常的处理
			dispatchException = new NestedServletException("Handler dispatch failed", err);
		}
		// 对下执行结果进行处理,包括视图的处理和异常的处理
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
		// 链式执行拦截器链的afterCompletion方法
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Throwable err) {
		// 拦截error类型异常,拦截后链式执行拦截器链的afterCompletion方法
		triggerAfterCompletion(processedRequest, response, mappedHandler,
				new NestedServletException("Handler processing failed", err));
	}
	// 进行资源清理
	finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
			// Instead of postHandle and afterCompletion
			if (mappedHandler != null) {
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		}
		else {
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}
}

方法流程图如下,我们对主要方法进行标志顺序:
在这里插入图片描述
接下里查看每一步流程具体是如何实现的。

1、校验请求

判断当前请求是否有上传需求,并返回保存到processedRequest中。

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
	// 判断当前请求是否包含文件上传的需求,如果是则执行后续逻辑
	if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
		// 判断当前请求是否是MultipartHttpServletRequest类型,如果是的话,就判断当前请求的类型是否是Request,如果是打印日志即可
		if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
			if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
				logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
			}
		}
		//判断是否有异常
		else if (hasMultipartException(request)) {
			logger.debug("Multipart resolution previously failed for current request - " +
					"skipping re-resolution for undisturbed error rendering");
		}
		// 将当前请求包装返回一个新的包装对象StandardMultipartHttpServletRequest
		else {
			try {
				return this.multipartResolver.resolveMultipart(request);
			}
			catch (MultipartException ex) {
				if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
					logger.debug("Multipart resolution failed for error dispatch", ex);
				}
				else {
					throw ex;
				}
			}
		}
	}
	return request;
}
2、获取处理器

调用 getHandler 获取可处理当前请求的处理器,通过HandlerMapping进行查找。在Spring MVC 中默认会加载三个请求处理类:

  • RequestMappingHandlerMapping:如果Controller层的类标注了@RequestMapping,则会扫描到
  • BeanNameUrlHandlerMapping:根据BeanName作为URL进行扫描
  • SimpleUrlHandlerMapping:如果上面两个都没有匹配到,则会调用它来进行处理

这几个类都是在初始化的时候设置成功的,它们具备相同的父类AbstractHandlerMapping。无论哪一个处理类最终都会调用getHandler方法,此方法在父类实现。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 判断当前处理器映射列表不为空
	if (this.handlerMappings != null) {
		// 遍历全部处理器映射
		for (HandlerMapping mapping : this.handlerMappings) {
			// 执行当前处理器映射的获取处理器方法,获取与本次请求适配的处理器执行链
			HandlerExecutionChain handler = mapping.getHandler(request);
			// 不为空直接返回,即便有多个处理器执行链匹配,也只返回第一个,处理器映射排在前面的优先返回
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}

跳转到 AbstractHandlerMapping.getHandler() 方法,根据request的请求路径获得对应的拦截器链。

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	//此方法留给子类实现,用于查找handler处理器,每个子类都有不同的实现,因此需要单独去查看
	Object handler = getHandlerInternal(request);
	// 如果handler为空,那么就使用默认的
	if (handler == null) {
		handler = getDefaultHandler();
	}

	if (handler == null) {
		return null;
	}
	// 如果返回的handler为string,则使用Spring容器实例化
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = obtainApplicationContext().getBean(handlerName);
	}

	// 查询匹配的拦截器,组装handler生成HandlerExecutionChain
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

	if (logger.isTraceEnabled()) {
		logger.trace("Mapped to " + handler);
	}
	else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
		logger.debug("Mapped to " + executionChain.getHandler());
	}

	// 判断是否是cors请求,cors是跨域请求
	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}

	return executionChain;
}

我们来看下是如何获得处理器,跟进到 AbstractHandlerMethodMapping.getHandlerInternal() 方法。

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	//通过路径帮助器获取当前request的请求路径
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	//增加读锁
	this.mappingRegistry.acquireReadLock();
	try {
		//查找当前请求的最佳匹配处理程序方法。如果找到多个匹配项,则选择最佳匹配项。
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		// 如果获取到的handler中bean是String则认为是beanName,则通过IoC容器获取该名称对应的Bean
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		//释放读锁
		this.mappingRegistry.releaseReadLock();
	}
}

得到对应的Handler处理器后,我们继续来看下是如何获得处理器执行链的。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

	// 通过路径帮助器获取当前request的请求路径
	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			//判断当前拦截器是否匹配该路径
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			//除MappedInterceptor之外的拦截器会拦截所有请求
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

通过以上方法,我们就得到了一个 HandlerExecutionChain 处理器执行链对象。

3、获取适配器

接着来看是如何根据当前请求的处理器执行链获取支持该处理器的适配器,回到 DispatcherServlet.getHandlerAdapter() 方法。此适配器集合共有三个具体实现子类,分别是:

  • HttpRequestHandlerAdapter:Controller层的类实现了HttpRequestHandler接口则能匹配到
  • SimpleControllerHandlerAdapter:Controller层的类实现了Controller接口则能匹配到
  • RequestMappingHandlerAdapter:Controller层的类添加了@RequestMapping注解则能匹配到,实际通过判断是否实现HandlerMethod接口

然后根据support方法来判断使用哪种适配器,并将对应的适配器对象返回。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
		// 遍历处理适配器列表,根据support方法来进行判断
		for (HandlerAdapter adapter : this.handlerAdapters) {
			// 当找到支持的适配器则返回
			if (adapter.supports(handler)) {
				return adapter;
			}
		}
	}
	// 未找到适配器则直接抛出异常
	throw new ServletException("No adapter for handler [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
4、拦截器前置处理

执行当前请求匹配到的所有拦截器 preHandler 方法

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// 获取当前处理器执行链中的所有拦截器
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
		// 正序遍历全部拦截器
		for (int i = 0; i < interceptors.length; i++) {
			HandlerInterceptor interceptor = interceptors[i];
			// 执行拦截器的preHandle方法,如果返回false则直接停止执行视为处理完成,触发拦截器的完成后方法
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			// 如果为true,拦截器索引设置为当前遍历索引
			this.interceptorIndex = i;
		}
	}
	// 全部执行完成,返回true,表示继续执行下一步
	return true;
}
5、处理请求

通过 AbstractHandlerMethodAdapter.handle() 方法跳转到 RequestMappingHandlerAdapter.handlerInternal() 方法,源码如下:

protected ModelAndView handleInternal(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	ModelAndView mav;
	//检查给定的请求是否已获取受支持的方法和所需的会话(如果有)
	checkRequest(request);

	// 是否需要在同步块中执行invokeHandlerMethod,如果没有使用到Session参数则不需要加锁
	if (this.synchronizeOnSession) {
		HttpSession session = request.getSession(false);
		if (session != null) {
			//获取Session级别的互斥锁保证线程安全
			Object mutex = WebUtils.getSessionMutex(session);
			synchronized (mutex) {
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}
	}
	else {
		//通过反射执行处理器方法,并将结果封装成ModelAndView
		mav = invokeHandlerMethod(request, response, handlerMethod);
	}

	//判断是否设置HTTP请求头允许缓存给定的秒数
	if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
		if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
			applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
		}
		else {
			prepareResponse(response);
		}
	}

	return mav;
}

很明显接下来的重点就是 invokeHandlerMethod 方法,看它是如何得到 ModelAndView 对象。

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
		//获取数据绑定器工厂,数据绑定器主要用于对入参数据进行绑定操作得到想要的数据
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		// 对当前方法的属性解析(如@SessionAttributes)并封装,用于保存model的对象属性值
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		//设置参数解析处理器
		if (this.argumentResolvers != null) {
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		//设置返回值解析处理器
		if (this.returnValueHandlers != null) {
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}
		invocableMethod.setDataBinderFactory(binderFactory);
		invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

		ModelAndViewContainer mavContainer = new ModelAndViewContainer();
		//设置当前request对应闪存
		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
		//初始化当前方法的@ModelAttribute注解相关
		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
		mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

		AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
		asyncWebRequest.setTimeout(this.asyncRequestTimeout);

		//异步管理器的设置
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.setTaskExecutor(this.taskExecutor);
		asyncManager.setAsyncWebRequest(asyncWebRequest);
		asyncManager.registerCallableInterceptors(this.callableInterceptors);
		asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

		if (asyncManager.hasConcurrentResult()) {
			Object result = asyncManager.getConcurrentResult();
			mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
			asyncManager.clearConcurrentResult();
			LogFormatUtils.traceDebug(logger, traceOn -> {
				String formatted = LogFormatUtils.formatValue(result, !traceOn);
				return "Resume with async result [" + formatted + "]";
			});
			invocableMethod = invocableMethod.wrapConcurrentResult(result);
		}

		//通过当前处理器处理请求
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		if (asyncManager.isConcurrentHandlingStarted()) {
			return null;
		}

		//从ModelAndViewContainer获取结果数据并封装成ModelAndView对象
		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
		webRequest.requestCompleted();
	}
}

来大概看一下是如何通过当前处理器执行请求的,根据 invokeAndHandle 方法。

  1. 将方法参数与请求中的值进行匹配,调用Controller层对应类实际方法进行处理并获得返回值。
  2. 设置响应状态值
  3. 调用具体HandlerMethodReturnValueHandler结果处理器对返回结果进行处理
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	//执行当前请求
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	//设置响应状态
	setResponseStatus(webRequest);

	if (returnValue == null) {
		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
			disableContentCachingIfNecessary(webRequest);
			mavContainer.setRequestHandled(true);
			return;
		}
	}
	else if (StringUtils.hasText(getResponseStatusReason())) {
		mavContainer.setRequestHandled(true);
		return;
	}

	mavContainer.setRequestHandled(false);
	Assert.state(this.returnValueHandlers != null, "No return value handlers");
	try {
		//调用具体HandlerMethodReturnValueHandler结果处理器对返回结果进行处理
		this.returnValueHandlers.handleReturnValue(
				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
	catch (Exception ex) {
		if (logger.isTraceEnabled()) {
			logger.trace(formatErrorForReturnValue(returnValue), ex);
		}
		throw ex;
	}
}

//将方法参数与请求中的值进行匹配,调用Controller层对应类实际方法进行处理并获得返回值
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	//从当前请求中获取处理器方法的参数值集合
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}
	//通过反射执行实际方法获得返回值
	return doInvoke(args);
}

通过如上步骤,我们就可以得到一个处理器执行后的 ModelAndView 对象。

6、视图设置

如果返回的ModelAndView对象没有视图,则设置默认视图名称。

private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
	//mv不为空并且没有视图
	if (mv != null && !mv.hasView()) {
		//获取默认的视图名称进行设置
		String defaultViewName = getDefaultViewName(request);
		if (defaultViewName != null) {
			mv.setViewName(defaultViewName);
		}
	}
}
7、拦截器后置处理

执行当前请求匹配到的所有拦截器 postHandler 方法

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
		throws Exception {
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
		for (int i = interceptors.length - 1; i >= 0; i--) {
			HandlerInterceptor interceptor = interceptors[i];
			interceptor.postHandle(request, response, this.handler, mv);
		}
	}
}
8、渲染视图及异常处理

先进行异常视图判断,然后执行视图渲染操作。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
		@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
		@Nullable Exception exception) throws Exception {
	// 判断是否是error视图
	boolean errorView = false;
	// 如果有异常,就进入异常处理逻辑,返回到异常页面
	if (exception != null) {
		if (exception instanceof ModelAndViewDefiningException) {
			// 直接使用异常中封装的ModelAndView作为最终的mv结果
			logger.debug("ModelAndViewDefiningException encountered", exception);
			mv = ((ModelAndViewDefiningException) exception).getModelAndView();
		}
		else {
			// 其他异常类型,先获取处理器
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			// 执行process处理其异常方法,获取处理了异常结果后得到的mv结果
			mv = processHandlerException(request, response, handler, exception);
			// 如果mv不为空,则说明返回了包含异常的视图
			errorView = (mv != null);
		}
	}

	// Did the handler return a view to render?
	// 如果mv不为空且mv没有标记为被清理
	if (mv != null && !mv.wasCleared()) {
		// 执行视图渲染的操作
		render(mv, request, response);
		// 如果是异常视图,渲染后需要清空请求属性中的异常信息
		if (errorView) {
			WebUtils.clearErrorRequestAttributes(request);
		}
	}
	else {
		if (logger.isTraceEnabled()) {
			logger.trace("No view rendering, null ModelAndView returned.");
		}
	}

	// 如果异步处理已经开始,则直接返回结束执行
	if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
		// Concurrent handling started during a forward
		return;
	}

	// 执行拦截器的AfterCompletion方法
	if (mappedHandler != null) {
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
}

我们来看下如何根据异常处理器获取ModelAndView对象,主要是通过注册的HandlerExceptionResolvers确定错误的ModelAndView。

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
		@Nullable Object handler, Exception ex) throws Exception {

	// 成功和错误响应可能使用不同的内容类型
	request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

	ModelAndView exMv = null;
	// 如果处理器异常解析器列表不为空
	if (this.handlerExceptionResolvers != null) {
		for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
			// 执行处理器异常解析器的解析异常方法,拿到解析的ModelAndView的结果
			exMv = resolver.resolveException(request, response, handler, ex);
			if (exMv != null) {
				break;
			}
		}
	}
	if (exMv != null) {
		if (exMv.isEmpty()) {
			// 设置异常属性到请求属性中
			request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
			return null;
		}
		if (!exMv.hasView()) {
			// 采用与doDispatch方法中相同的处理逻辑来给很具请求获取默认视图名
			String defaultViewName = getDefaultViewName(request);
			if (defaultViewName != null) {
				exMv.setViewName(defaultViewName);
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Using resolved error view: " + exMv, ex);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Using resolved error view: " + exMv);
		}
		// 暴露异常信息到请求属性中
		WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
		return exMv;
	}
	// 如果没有处理器异常解析器,则原封不动抛出原始异常,交给web框架处理
	throw ex;
}

接着来看下视图是如何进行渲染的,主要是通过执行视图的渲染方法。每种模板引擎都有其对应的视图实现,视图渲染对应于模板引擎的渲染模板。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	// 先通过Locale解析器获取请求对应的Locale
	Locale locale =
			(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
	// 设置获取的Locale为响应的Locale
	response.setLocale(locale);

	View view;
	// 如果mv中的视图为视图名,则获取这个视图名
	String viewName = mv.getViewName();
	if (viewName != null) {
		// 把视图名解析为视图
		view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
		// 无法根据视图名解析视图时抛出异常
		if (view == null) {
			throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
					"' in servlet with name '" + getServletName() + "'");
		}
	}
	else {
		// 如果不是视图名,而直接是一个视图类型,则获取视图
		view = mv.getView();
		if (view == null) {
			throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
					"View object in servlet with name '" + getServletName() + "'");
		}
	}

	if (logger.isTraceEnabled()) {
		logger.trace("Rendering view [" + view + "] ");
	}
	try {
		// 如果mv中的status不为空,则把其设置为响应的状态码,
		if (mv.getStatus() != null) {
			response.setStatus(mv.getStatus().value());
		}
		// 执行视图的渲染方法,每种模板引擎都有其对应的视图实现,视图渲染对应于模板引擎的渲染模板
		view.render(mv.getModelInternal(), request, response);
	}
	catch (Exception ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Error rendering view [" + view + "]", ex);
		}
		throw ex;
	}
}
9、资源清理

主要释放给定请求使用的所有资源(如果有),这里就不细看了。

总结

最后用一张 Spring MVC 处理流程图来总结,一切尽在图中。
在这里插入图片描述
参考链接:
https://segmentfault.com/q/1010000000210417
https://www.cnblogs.com/shawshawwan/p/9002126.html
https://www.cnblogs.com/lgk8023/p/6427977.html
https://blog.csdn.net/Apeopl/article/details/84372771
https://www.cnblogs.com/fangjian0423/p/springMVC-directory-summary.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值