Spring MVC框架体系结构详解

概述

无论是基于Spring Boot还是基于SSM框架的Web开发,都涉及到了Spring MVC框架的使用。Spring MVC是Spring Framework的其中一个产品,是一个强大灵活的基于MVC的Web框架,它包含以下优点:

  • 请求统一通过内置的DispatcherServlet处理,开发者无需编写难以管理、维护的FilterServlet类。
  • 分工明确,包括控制器、验证器、命令对象、模型对象、处理程序映射视图解析器等等,每一个功能实现由一个专门的对象负责完成
  • 自带Spring框架的IoC和AOP功能,有效降低了代码的耦合度
  • 配置相对于传统的web.xml更加简单方便,维护起来更加容易
  • 支持国际化
  • 支持多种视图技术

本文假定读者有一定的Spring MVC开发经验。

DispatcherServlet

DispatcherServlet是整个Spring MVC的核心,它的继承体系如下:
Spring MVC框架体系结构详解_第1张图片
可以看出它实现了javax.servlet.Servlet接口,所以它本质上是一个Servlet,并可通过web.xml文件注册到Servlet容器上。
并且,它也实现了ApplicationContextAwareEnvironmentAware接口,所以在Spring容器在创建DispatcherServlet实例的时候,也会将IoC容器ApplicationContext和环境参数容器Environment注入到DispatcherServlet,使其可以利用到Spring的IoC容器。

一般在开发基于Spring MVC的Web应用的时候,我们一般会在web.xml文件中这么定义这个DispatcherServlet

<servlet>
	<servlet-name>DispatcherServletservlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
	<load-on-startup>1load-on-startup>
	<init-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>classpath:applicationContext.xmlparam-value>
    init-param>
servlet>
<servlet-mapping>
    <servlet-name>DispatcherServletservlet-name>
    <url-pattern>/url-pattern>
servlet-mapping>

可以看出和一般的Servlet配置没什么区别,我们只需要将所有的URL请求产生的HttpServletRequest都由这个DispatcherServlet进行处理就可以了。同样和其它Servlet类似,DispatcherServlet是单例的,多个请求由同一个DispatcherServlet处理。

DispatcherServlet处理请求的流程可以用以下图片概括:
Spring MVC框架体系结构详解_第2张图片
其处理流程可以概括为:

  1. 客户端发起请求后,由Servlet容器将请求包装为HttpServletRequest对象,然后Servlet容器通过反射创建DispatcherServlet对象,调用其init方法并传入ServletConfig对象(存储了web.xml文件定义的init-param参数)初始化DispatcherServlet,然后调用其service方法并传入HttpServletRequestHttpServletResponse进行服务。
  2. DispatcherServlet接收到Servlet容器传来的HttpServletRequestHttpServletResponse后,会通过其URL,找到其对应的HandlerMapping
  3. 调用HandlerMapping实例的getHandler方法并传入HttpServletRequest,获取HandlerExecutionChain(包含一个Handler对象和多个HandlerInterceptor
  4. 执行HandlerExecutionChain包含的HandlerInterceptor中的前置处理方法
  5. 根据HandlerExecutionChain包含的Handler找到其对应的HandlerAdaptor
  6. 调用该HandlerAdaptorhandler方法并传入HttpServletRequestHttpServletResponseHandler对象,返回ModelAndView对象。
  7. 如果ModelAndView指定的是视图名称而不是View实例(一般情况),那么调用ViewResolve的方法得到View对象。
  8. 调用View对象的render并传入模型数据,渲染视图然后写入到HttpServletResponse。请求处理完成。

我们来分别讲解上述元件的基本构造。


HandlerMapping

HandlerMapping负责帮助DispatcherServlet进行HTTP请求的URL到具体Handler匹配,也就是Web请求到Handler(我们熟知的Controller其实就是Handler的一种)的映射关系

HandlerMapping有以下几种类型:

  • SimpleUrlHandlerMapping,可以用来处理具体的URL与Handler之间的映射
  • RequestMappingHandlerMapping,用来处理具体的URL与基于注解的Controller之间的映射关系,@RequestMapping就是通过这个HandlerMapping实现的。

其它类型相对来说用的比较少,这里就不再阐述了。

一个DispatcherServlet是可以拥有多个HandlerMapping实例的,那这么多HandlerMapping实例怎么才能找到对应的HandlerMapping呢?
答案很简单,只需要遍历HandlerMapping集合就可以了:
DispatcherServlet通过List来保存HandlerMapping

@Nullable private List<HandlerMapping> handlerMappings;

HandlerMapping本质上是一个接口,它只定义了一个抽象方法:

@Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

注意其返回值是@Nullable注解修饰的,也就是说,只需要根据返回值是否为null来判断有没有找到合适的HandlerMapping就可以了。DispatcherServlet也的确是这么实现的:

@Nullable
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;
}

如果要是没有找到合适的HandlerMapping,那么就会向客户端返回一个404错误
虽然HandlerMapping接口没有实现Ordered接口,但是其官方实现类都实现了Ordered接口,在HandlerMapping对象添加到List集合时,会根据Ordered接口定义的方法的返回值来进行排序,优先级高的HandlerMapping会排在前面。

HandlerMapping在找到合适的HandlerHandlerInterceptor后,会将其包装为一个HandlerExecutorChain对象并返回。
HandlerExecutionChain可以理解为一个HTTP请求处理链,它包含一个Handler实例和多个HandlerInterceptor实例:

private final Object handler;
@Nullable private HandlerInterceptor[] interceptors;
@Nullable private List<HandlerInterceptor> interceptorList;
private int interceptorIndex = -1;

可以发现HandlerObject类型的,之所以这么设计是为了可扩展性考虑的,通过这种机制不仅仅可以实现Spring MVC自带的Controller,还可以通过扩展实现StrutsAction


HandlerAdaptor

刚才已经提到HandlerObject类型的,那么如何执行Handler处理我们编写的业务逻辑呢?HandlerAdaptor就是为此诞生的,它作为一个适配器,屏蔽了不同Handler类型给DispatcherServlet带来的“麻烦”,它可以看成是一个DispatcherServletHandler之间的“中间人”。

HandlerAdaptor本身是一个接口,它定义了以下几个抽象方法:

public interface HandlerAdapter {
	boolean supports(Object handler);
	@Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, 
					              Object handler) throws Exception;
	long getLastModified(HttpServletRequest request, Object handler);
}

其中supports方法就是用来判断这个HandlerAdaptor支不支持处理这个Handler
DispatcherServlet在遍历HandlerAdaptor集合的时候,会首先调用supports方法并传入HandlerExecutorChain中的Handler获取其判定结果,如果为false,那么继续遍历下一个HandlerAdaptor。如果返回结果为true,那么就会调用其handle方法并获取ModelAndView对象。

至于getLastModified方法,它的主要目的只是为返回给客户端的Last-Modified这个HTTP响应头提供其相应的时间戳,如果不想支持此功能直接返回-1即可。

我们以处理Controller类型的HandlerAdaptorSimpleControllerHandlerAdapter为例看看它是如何处理的:

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return ((Controller) handler).handleRequest(request, response);
	}

	@Override
	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}
}

很简单,supports方法只需要判断这个Handler是不是Controller类型的就好了。至于其handler方法,就是进行一个强制类型转换然后调用ControllerhandlerRequest方法。


HandlerInterceptor

HandlerInterceptor可以看成是一种DispatcherServlet内部的Filter,它可以在Handler执行前后对处理流程进行拦截。

HandlerInterceptor本质上同样是一个接口,它定义了三种方法:

public interface HandlerInterceptor {
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
			Object handler) throws Exception {
		return true;
	}
	
	default void postHandle(HttpServletRequest request, HttpServletResponse response, 
			Object handler, @Nullable ModelAndView modelAndView) throws Exception {
	}
	
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
			Object handler, @Nullable Exception ex) throws Exception {
	}
}

这三种方法均是拦截方法:

  • preHandler:这个拦截方法会在调用HandlerAdaptor之前执行DispatcherServlet会根据该方法的boolean返回值决定是否继续执行后续流程。如果返回值为true,那么后继的HandlerInterceptorpreHandler会继续调用,如果不存在后继HandlerInterceptor那么会直接执行对应的HandlerAdaptor。如果返回值为false或是抛出了异常,那么后继HandlerInterceptorHandlerAdaptor就不会继续执行了。
  • postHandler:Handler执行完毕,视图的解析和渲染之前执行。在这个时候我们就可以处理HandlerAdaptor返回的ModelAndView了,例如可以修改模型数据或是视图。但是不可阻断后续处理流程
  • afterCompletion:Dispatcher已经完成了视图渲染后,不管是否发生了异常该方法都会得到执行。如果上述处理流程是以异常结束的,那么在这里我们就可以得到这个异常的引用并处理这个异常了。

在Spring 5.0以后的Web开发中,我们可以通过实现WebMvcConfigurer接口来添加HandlerInterceptor


HandlerExceptionResolver

HandlerExceptionResolver用于在Handler查找和Handler执行期间提供异常处理机制。如果在Handler执行期间没有抛出任何异常,就会返回ModelAndView,而一旦抛出异常,HandlerExceptionResolver就立刻接手异常的处理,并同样返回一个ModelAndView

HandlerExceptionResolver接口源代码如下:

public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, 
					          @Nullable Object handler, Exception ex);
}

该接口只定义了一个方法resolveException,在Dispatcher调用时会传入Handler抛出的异常的引用和Handler实例。


ViewResolver

ViewResolver即视图定位器,它的主要职责是根据HandlerAdaptor返回的ModelAndView中的逻辑视图名,为DispatcherServlet提供一个View实例。当然如果HandlerModelAndView直接指定了View实例,那么就不会调用ViewResolver

ViewResolver接口源代码如下:

public interface ViewResolver {
	@Nullable
	View resolveViewName(String viewName, Locale locale) throws Exception;
}

ViewResolver实现类只需要根据逻辑视图名和Locale(用于实现国际化)返回相应的View实例即可。

大部分ViewResolve实现类都继承了AbstractCachingViewResolver,也就是说大部分ViewResolve实现类都加入了View缓存的功能,因为每次实例化View是一个开销比较大的操作。

常用的ViewResolve实现类有:

  • InternalResourceViewResolver:它对应InternalResourceView,用于处理JSP模板。
  • FreeMarkerViewResolver:用于定位Freemarker类型的视图。
  • ResourceBundleViewResolver:该实现类构建于ResourceBoundle,支持国际化。

View

View即视图,负责通过模型数据向HttpServletResponse写入用户可见的界面,Spring MVC提供了基于JSP、Velocity、Freemarker、Excel、PDF等视图技术。

View接口源代码如下:

public interface View {
	String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
	String PATH_VARIABLES = View.class.getName() + ".pathVariables";
	String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";

	@Nullable
	default String getContentType() {
		return null;
	}

	void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
			throws Exception;
}

其中,getContentType用于向HTTP响应头写入content-type数据,好让浏览器知道视图是什么类型,例如HTTP页面返回的就应当是text/html
render方法用于根据模型数据渲染视图,并写入到响应体。

你可能感兴趣的:(Spring MVC框架体系结构详解)