1.什么是Spring MVC
Spring MVC是基于Servlet API构建的原始Web框架,从一开始就包含在了Spring框架中,作为其中的一部分,对应为spring-webmvc包 。它遵循Model-View-Controller(MVC)设计模式,提供了一种清晰的方式来分离业务逻辑、用户界面表示以及用户输入处理。
Model-View-Controller(MVC):是一种软件架构模式,用于组织代码并分离关注点,广泛应用于用户界面的开发中。该模式将相关程序逻辑划分为三个相互关联的组成部分:模型(Model)、视图(View)和控制器(Controller) 。
模型(Model):承担着管理应用程序数据和业务逻辑的重要责任。包含的主要职责有:①作为数据访问对象(Data Access Objects,DAO),用于与数据存储交互,以及实体类来表示应用程序中的核心数据结构;②负责数据的通知和变化,确保数据变化时其他组件(如视图和控制器)能够及时获取到更新的数据。
视图(View):负责将模型的数据以用户友好的方式呈现给用户,并接收用户的输入。视图通常包括各种用户界面组件,如文本框、按钮、下拉菜单等,以及对数据的展示和呈现逻辑。
控制器(Controller):它是模型和视图之间的桥梁,负责解释用户的输入,控制用户输入的流程,并更新模型和视图。控制器接收来自视图的动作事件,然后根据这些事件来决定如何操作模型(例如更新数据)以及选择哪个视图来更新用户界面。
2.Spring MVC核心概念
2.1 核心组件
DispatcherServlet:前端控制器,负责接收客户端请求,并将请求分发给合适的处理程序。作为请求处理流程的入口,其实现了Servlet接口,对应UML类图如下所示:
HandlerMapping:处理器映射器,用于查找处理请求的控制器。基础实现类有:
- RequestMappingHandlerMapping:根据 @RequestMapping 注解来映射处理器
- SimpleUrlHandlerMapping:支持根据URL与Bean名称/Bean实例来映射处理器
- BeanNameUrlHandlerMapping:基于 URL 的 Bean 名称来映射处理器
其中RequestMappingHandlerMapping是最常用的。
HandlerAdapter:处理器适配器,负责调用具体的控制器方法。最常用的实现类为RequestMappingHandlerAdapter。
ViewResolver:视图解析器,用于解析逻辑视图名,返回具体的视图对象。
- InternalResourceViewResolver:支持JSP视图,可以集成JSTL。
- UrlBasedViewResolver:通用视图解析器,可以创建任意类型的视图。
- FreeMarkerViewResolver:用于解析FreeMarker模板。
- BeanNameViewResolver:根据视图名从Spring容器中查找视图bean。
ModelAndView:包含模型数据和视图名的对象,用于将数据传递给视图层。
2.2 请求处理流程
Spring MVC的请求处理核心流程如下图所示:
总结下来就是,当客户端发起请求时,请求首先到达DispatcherServlet,DispatcherServlet将执行以下操作:
- 请求分发:根据请求的URL找到对应的HandlerMapping,HandlerMapping将返回一个HandlerExecutionChain对象,该对象包含了一个或多个Handler对象以及拦截器(如果有的话)。
- 调用处理器:DispatcherServlet使用HandlerAdapter来调用Handler对象中的方法。
- 处理请求:Handler对象处理请求后返回一个ModelAndView对象,该对象包含了模型数据和视图名。
- 视图渲染:DispatcherServlet使用ViewResolver解析视图名,得到具体的视图对象,然后将模型数据传给视图进行渲染。
- 返回响应:最终的HTML页面被返回给客户端。
3.WEB示例
在开始源码分析之前,我们先看一个web项目示例。该项目基于web.xml的方式进行配置的(servlet3.0开始支持注解形式,受限于篇幅将在后续文章中分析),具体内容如下:
项目目录:
└─src
└─main
├─java
│ └─spring
│ └─mvc
│ └─controller
│ └─TestRestController
├─resources
│ └─dispatcher-servlet.xml
└─web
└─WEB-INF
└─web.xml
web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- 配置Spring MVC的核心控制器 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置dispatcher处理的请求路径信息 -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
dispatcher-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- 启用基于注解的控制器 -->
<mvc:annotation-driven />
<!-- 组件扫描 -->
<context:component-scan base-package="spring.mvc.controller" />
</beans>
TestController
package spring.mvc.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/test")
public class TestRestController {
@GetMapping("/message")
public String test() {
return "hello mvc";
}
}
4.Spring MVC初始化
Spring MVC初始化分为两部分,第一部分是WebApplicationContext初始化(即Spring容器),第二部分是DispatcherServlet初始化。因为Spring MVC是Spring框架的子模块,因此其运行需要依赖Spring容器,接下来将先从WebApplicationContext初始化开始进行分析。
4.1 WebApplicationContext初始化
WebApplicationContext初始化依赖于tomcat对servlet进行初始化步骤,因此我们在web.xml中配置的servlet:DispatcherServlet
在初始化时会触发WebApplicationContext的初始化,其UML时序图如下图所示:
从上图可知,WebApplicationContext初始化的核心步骤在DispatcherServlet类的父类FrameworkServlet类的createWebApplicationContext()方法
中,其核心源码如下:
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// 获取使用的Spring容器类,对应为内部默认的类型:XmlWebApplicationContext
Class<?> contextClass = getContextClass();
// 省略部分代码...
// 通过反射的方式实例化Spring容器
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
// 设置环境变量信息
wac.setEnvironment(getEnvironment());
// 设置父容器 通常情况下我们会通过在web.xml中设置listener为ContextLoaderListener,进行Spring父容器初始化,用于service、mapper等Bean的管理
// 通过上述方式配置的Spring容器即为父容器
wac.setParent(parent);
// 获取上下文配置路径,从web.xml中的servlet->init-param获取配置的信息
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 将servlet容器的上下文、配置等信息设置给Spring容器,
// 并通过执行XmlWebApplicationContext的父类AbstractApplicationContext#refresh()方法,进行Spring容器初始化
configureAndRefreshWebApplicationContext(wac);
return wac;
}
其内部调用的configureAndRefreshWebApplicationContext()
方法,其核心源码如下:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// 省略部分代码...
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
// 添加应用消息监听器,用于接收容器启动事件ContextRefreshedEvent
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// 提前将servlet容器涉及的相关配置信息初始化到Spring容器的属性集里
// 确保Spring容器初始化能取到依赖于servlet容器的配置属性值
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
// 扩展口,用于在Spring容器初始化前做一些扩展操作
postProcessWebApplicationContext(wac);
applyInitializers(wac);
// 进行Spring容器的初始化
wac.refresh();
}
4.2 DispatcherServlet初始化
DispatcherServlet初始化此处涉及的不是servlet的初始化(tomcat启动后,会将配置在web.xml中的servlet进行实例及初始化,从前面步骤中完成的Spring容器的初始化),而是对HandlerMapping、HandlerAdapter等用于请求转发、处理操作的相关配置解析初始化操作,以及这部分配置信息与DispatcherServlet结合,完成DispatcherServlet完整初始化过程。
接下来将针对这两部分进行分析。
4.2.1 配置解析
配置涉及HandlerMapping、HandlerAdapter、ViewResolver的解析及生成,整体涉及BeanDefinition扫描、加载、注册,以及对应Bean的实例及初始化,接下来将从这两部分进行分析。
4.2.1.1 AnnotationDrivenBeanDefinitionParser
HandlerMapping、HandlerAdapter、ViewResolver涉及的BeanDefinition扫描、加载、注册是由AnnotationDrivenBeanDefinitionParser
类负责的,其内部核心方法为parse()。
AnnotationDrivenBeanDefinitionParser这个类通过解析web.xml中的servlet->init-param中设置的配置文件,完成配置类的BeanDefinition注册。主要完成了:
①根据xml配置文件进行自定义配置的BeanDefinition注册;
②默认配置的BeanDefinition注册;
其核心源码如下:
public BeanDefinition parse(Element element, ParserContext context) {
Object source = context.extractSource(element);
XmlReaderContext readerContext = context.getReaderContext();
// 省略部分代码...
// 进行自定义配置的BeanDefinition注册
// RequestMappingHandlerMapping
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
// 省略属性赋值相关代码...
// RequestMappingHandlerMapping BeanDefinition注册
readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
// 省略部分代码...
// RequestMappingHandlerAdapter
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
// 省略属性赋值相关代码...
// RequestMappingHandlerAdapter BeanDefinition注册
readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
// 省略部分代码...
// 进行默认组件的BeanDefinition注册
// 涉及BeanNameUrlHandlerMapping、HttpRequestHandlerAdapter、
// SimpleControllerHandlerAdapter、HandlerMappingIntrospector、
// AcceptHeaderLocaleResolver、FixedThemeResolver、
// DefaultRequestToViewNameTranslator、SessionFlashMapManager
MvcNamespaceUtils.registerDefaultComponents(context, source);
// 省略部分代码...
return null;
}
BeanDefinition注册完成后,需要进行Bean的实例及初始化,在这个过程中完成了如url请求路径与具体Controller的RequestMapping方法的对应关系解析等操作。接下来针对核心的HandlerMapping、HandlerAdapter涉及的Bean实例及初始化过程中涉及的配置解析过程进行分析。
4.2.1.2 HandlerMapping
HandlerMapping对应的核心实现类为RequestMappingHandlerMapping
,接下来通过这个类对应Bean的生成来分析请求URL与具体Controller的RequestMapping方法映射解析逻辑的分析。
RequestMappingHandlerMapping的UML类图如下所示:
通过此图可以看到,它实现了InitializingBean
接口,因此在Bean的创建过程中的初始化步骤会回调其实现的afterPropertiesSet()
方法,以完成Bean初始化,RequestMappingHandlerMapping通过此方法完成了请求URL与具体Controller的RequestMapping方法映射解析逻辑,其UML时序图如下所示:
从时序图可以看到,完成映射逻辑构建的核心方法为AbstractHandlerMethodMapping
类的detectHandlerMethods()
方法,其核心源码如下:
protected void detectHandlerMethods(Object handler) {
// 获取BeanName对应的类类型
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
// 获取实际对应的类,此处可能会涉及到代理,所以需要获得被代理类
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 获取类及其父类、接口方法上有RequestMapping注解的方法及RequestMappingInfo信息对应关系
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// 获取方法上有RequestMapping注解的RequestMappingInfo信息
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
// 省略日志输出代码...
// 将类方法上有RequestMapping注解的方法及RequestMappingInfo信息对应关系注册到MappingRegistry中
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
其中,涉及到解析方法上@RequestMapping
注解的方法getMappingForMethod(),其实现类为RequestMappingHandlerMapping
。
最终解析得到的RequestMapping注解标记的方法及其RequestMappingInfo信息注册到了MappingRegistry
的registry属性
中,该属性的类型为Map<T, MappingRegistration>,对应key为RequestMappingInfo信息。
4.2.1.3 HandlerAdapter
HandlerAdapter对应的常用核心实现类为RequestMappingHandlerAdapter
,接下来通过这个类对应Bean的生成来分析请求的参数、返回值解析逻辑是如何进行初始化的。
RequestMappingHandlerAdapter的UML类图如下所示:
通过此图可以看到,它实现了InitializingBean
接口,因此在Bean的创建过程中的初始化步骤会回调其实现的afterPropertiesSet()
方法,以完成Bean初始化。
核心源码如下:
public void afterPropertiesSet() {
// 初始化@ControllerAdvice注解标记的Controller切面Bean(非AOP的切面Bean)缓存
initControllerAdviceCache();
// 设置默认参数解析器
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
// 设置默认绑定参数解析器
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
// 设置默认返回值处理器
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
从源码可以看到,主要完成了默认参数解析器、默认返回值处理器的设置。
参数解析器(HandlerMethodArgumentResolver)
负责将HTTP请求中的数据转换成处理器方法(Controller中的方法)所需的参数类型。它能够解析请求中的查询字符串、表单数据、路径变量、请求体等,并将其转换为Java对象。
如下示例所示:
@GetMapping("/goods/{id}")
public String greet(@PathVariable String id, @RequestParam String name) {
return id + name;
}
这里的@PathVariable和@RequestParam就是通过参数解析器来解析路径变量和查询参数的。
返回值处理器(HandlerMethodReturnValueHandler)
负责处理控制器方法的返回值,并将其转换为合适的HTTP响应。这包括将返回的对象序列化为响应体(例如JSON或XML),或者决定视图名称以渲染HTML页面。
如下示例所示:
@GetMapping("/goods")
public Goods getUser(@RequestParam("id") int id) {
return goodsRepo.findById(id);
}
这里如果没有显式地指定如何处理返回值,Spring MVC会自动使用一个适合的返回值处理器来将Goods对象转换为JSON格式并发送给客户端。
4.2.2 DispatcherServlet初始化
在前面WebApplicationContext容器(Spring容器)初始化过程中,添加了一个应用消息监听器ContextRefreshListener
,用于监听Spring容器初始化完成事件。
当Spring容器初始化完成时,会发送ContextRefreshedEvent消息,ContextRefreshListener接收到消息后,调用FrameworkServlet#onApplicationEvent()
方法,该方法内部最终调用的是DispatcherServlet#onRefresh()
方法,进行HandlerMapping、HandlerAdapter、ViewResolver等与DispatcherServlet的绑定。
ContextRefreshListener源码如下:
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
// 接收容器启动事件
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
DispatcherServlet#onRefresh()相关源码如下:
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// 初始化DispatcherServlet需要用到的HandlerMapping、HandlerAdapter、ViewResolver等内容
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
5.Spring MVC请求处理流程
由于Spring MVC是基于servlet API实现的,因此请求经过tomcat都会转发到DispatcherServlet上,以Get请求为例(其余请求类型大流程一致)其UML时序图如下所示:
从上图可以看到,针对请求最核心的解析、处理逻辑为DispatcherServlet的doDispatch()
方法,接下来将针对此方法结合Spring MVC的核心流程进行分析。
5.1 doDispatch()
该方法涉及的核心步骤有七步:
- 根据请求URL等信息从HandlerMapping中找到对应的HandlerExecutionChain;
- 根据HandlerExecutionChain中的Handler找到对应的HandlerAdapter;
- 执行前置拦截器HandlerInterceptor;
- 执行具体的业务逻辑Handler;
- 执行后置拦截器HandlerInterceptor;
- 处理返回结果;
- 执行任务执行完成拦截器HandlerInterceptor。
其核心源码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 检查是否为分段请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 1.根据请求URL等信息从HandlerMapping中找到对应的HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
// 省略部分代码...
// 2.根据HandlerExecutionChain中的Handler找到对应的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 省略部分代码...
// 3.执行前置拦截器HandlerInterceptor
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 4.执行具体的业务逻辑Handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 省略部分代码...
applyDefaultViewName(processedRequest, mv);
// 5.执行后置拦截器HandlerInterceptor
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
// 省略部分代码...
// 6.处理返回结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 7.执行任务执行完成拦截器HandlerInterceptor
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
// 省略部分代码...
}
接下来针对其中的重点步骤进行详细分析。
5.2 HandlerMapping原理
HandlerMapping负责将客户端的 HTTP 请求映射到相应的处理器(Handler),作为用户请求与应用处理器之间的桥梁,为Spring MVC提供了灵活管理不同类型请求与处理器之间的映射关系。
我们以最常用的核心实现类RequestMappingHandlerMapping为示例进行分析,其UML时序图如下所示:
从时序图中,可以看到最核心的步骤为步骤7、步骤10,接下来对其源码进行分析。
5.2.1 lookupHandlerMethod()
该方法核心是根据请求信息,从预先扫描注册好的请求路径与处理器映射mappingRegistry中找到符合的HandlerMethod处理器,以下为相关匹配逻辑:
- 路径精确匹配:如果请求路径与映射路径完全一致,则这种映射具有最高的优先级。
- 路径变量数量:如果存在路径变量(如/user/{id}中的{id}),那么路径变量较少的映射可能被认为更加具体,因而优先级更高。
- 路径前缀长度:最长的路径前缀匹配可能会被优先考虑。
- HTTP方法:确保请求的HTTP方法(GET, POST等)与映射定义的方法相匹配。
- 消费和生产的内容类型:映射可能指定了处理器能处理的内容类型(如application/json),如果请求的内容类型与之匹配,则该映射可能优先。
其源码如下:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 先尝试直接根据请求路径查找请求映射信息RequestMappingInfo
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
// 如果找了直接匹配的请求映射信息,则遍历直接匹配上的请求处理器映射信息,根据请求信息(请求方法、参数、URL等)构建符合的请求映射信息RequestMappingInfo
// 进而构建出Match
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 如果没找到,则遍历注册的请求处理器映射信息,根据请求信息(请求方法、参数、URL等)构建符合的请求映射信息RequestMappingInfo
// 进而构建出Match
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
// 如果找到了多个匹配项,则使用MatchComparator,根据路径变量数量、路径前缀长度、消费和生产的内容类型等进行匹配,找到最佳匹配项
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
// 如果是预检请求(OPTION请求,用于检测是否运行跨域发送),且存在有跨域配置的Match,则返回空处理器
if (CorsUtils.isPreFlightRequest(request)) {
// 省略部分代码...
// 查找是否有跨域配置的Match
}
else {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
// 省略部分代码...
// 存在两个相同的最佳匹配项,则抛出异常
}
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.getHandlerMethod();
}
// 如果未找到,则根据请求信息抛出相关异常
}
5.2.2 getHandlerExecutionChain()
此方法用于构建HandlerExecutionChain,主要目的是给对应HandlerMethod设置匹配的HandlerInterceptor拦截器
,用于HandlerMethod执行前后进行拦截处理。其源码如下:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// 设置拦截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
5.3 HandlerAdapter
HandlerAdapter 的作用主要是作为适配器,它负责将接收到的HTTP请求交给适当的控制器(Handler)处理。它是一个接口,定义了处理请求的标准方法。
具体来说,它的作用包括:
7. 适配不同的处理器:不同的控制器可能有不同的实现方式,HandlerAdapter 可以适配多种类型的处理器,使得Spring MVC能够支持多种不同的控制器实现。通过supports()方法,HandlerAdapter 可以判断自己是否支持某个特定的处理器。
8. 处理请求:通过 handle()方法,HandlerAdapter 负责调用处理器的方法来处理请求,并返回一个 ModelAndView 对象,该对象包含了视图名和模型数据。
9. 获取最后修改时间:可以通过 getLastModified()方法获取资源的最后修改时间,常用于于客户端请求缓存控制。
此接口常用的核心实现类为RequestMappingHandlerAdapter
,接下来针对该类对于处理请求的逻辑进行分析,其核心源码如下:
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 省略部分代码...
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
// 设置参数处理器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
// 设置返回值处理器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 省略部分代码...
// 返回结果容器:暂存返回数据用
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
// 省略部分代码...
// 省略部分代码...
// 处理请求:进行参数处理、业务逻辑调用、返回值处理
invocableMethod.invokeAndHandle(webRequest, mavContainer);
//省略部分代码...
// 基于返回结果,构建ModelAndView
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 使用反射的方式调用目标业务方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
// 省略部分代码...
try {
// 对返回值做格式化等处理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
// 省略部分代码...
}
5.4 HandlerInterceptor
HandlerInterceptor作为Spring MVC提供扩展接口,用于开发者在请求处理的前后执行一些操作,例如权限校验、日志记录等。
其核心方法有三个,分别在请求处理的不同阶段调用:
- preHandle():在请求处理之前调用。返回true表示继续流程(如调用下一个拦截器或处理器),返回false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器。
- postHandle():在请求处理之后、视图渲染之前调用。此方法可以对ModelAndView对象进行操作。
- afterCompletion():在整个请求完成,即视图渲染结束后调用。主要作用是用于清理资源等。
2.postHandle():在请求处理之后、视图渲染之前调用。此方法可以对ModelAndView对象进行操作。
以下为实现示例,用于检查用户的登录状态:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 检查session中是否存在用户信息
if (request.getSession().getAttribute("user") != null) {
// 用户已登录,放行
return true;
} else {
// 用户未登录,重定向到登录页面
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 可以在这里对ModelAndView进行操作
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 请求完成后的操作
}
}
6.总结
Spring MVC作为Spring框架的一部分,遵循MVC设计模式,用于提供Web框架功能。基于Servlet API的核心实现为DispatcherServlet,作为前端控制器,用于分发前端请求。内部通过HandlerMapping、HandlerAdapter、ViewResolver,完成了请求解析映射、请求业务逻辑调用、请求返回值处理解析。并且还通过HandlerInterceptor,提供了请求业务逻辑调用前、后、完成(视图渲染完成)三个阶段的扩展口,便于程序员自定义请求业务逻辑的执行。