- 三哥
内容来自【自学星球】
欢迎大家来了解我的星球,和星主(也就是我)一起学习 Java ,深入 Java 体系中的所有技术。我给自己定的时间是一年,无论结果如何,必定能给星球中的各位带来点东西。
想要了解更多,欢迎访问👉:自学星球
--------------SSM系列源码文章及视频导航--------------
创作不易,望三连支持!
SSM源码解析视频
👉点我
Spring
SpringMVC
MyBatis
---------------------【End】--------------------
我们知道 SpringMVC 只配置了一个 Servlet 即 DispatcherServlet ,它会拦截我们所有的 URL 请求统一在其内部进行调用处理。
所以我们分析 SpringMVC 的请求流程理所应当会来到 DispatcherServlet 类中的 service 方法。
但本次我想从 doDispatch 方法开始分析,因为最终不论是何种请求方法都会来到这个方法进行处理调用。
org.springframework.web.servlet.DispatcherServlet#doDispatch
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 {
// 这里判断当前请求是否为一个文件请求,这里的判断方式就是要求当前请求满足两点:①请求
// 方式是POST;②判断contentType是否以multipart/开头。如果满足这两点,那么就认为当前
// 请求是一个文件请求,此时会将当前请求的request对象封装为一个
// MultipartHttpServletRequest对象,这也是我们在定义文件请求的Controller时
// 能够将request参数写为MultipartHttpServletRequest的原因。这里如果不是文件请求,
// 那么会将request直接返回。
processedRequest = checkMultipart(request);
// 这里判断原始request与转换后的request是否为同一个request,如果不是同一个,则说明
// 其是一个文件请求
multipartRequestParsed = (processedRequest != request);
// 这里getHandler()方法就是通过遍历当前Spring容器中所有定义的HandlerMapping对象,
// 通过调用它们的getHandler()方法,看当前的HandlerMapping能否将当前request映射
// 到某个handler,也就是某个Controller方法上,如果能够映射到,则说明该handler能够
// 处理当前请求
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 如果每个HandlerMapping都无法找到与当前request匹配的handler,那么就认为
// 无法处理当前请求,此时一般会返回给页面404状态码
noHandlerFound(processedRequest, response);
return;
}
// 通过找到的handler,然后在当前Spring容器中找到能够支持将当前request请求适配到
// 找到的handler上的HandlerAdapter。这里需要找到这样的适配器的原因是,我们的handler
// 一般都是Controller的某个方法,其是一个Java方法,而当前request则是一种符合http
// 协议的请求,这里是无法直接将request直接应用到handler上的,因而需要使用一个适配器,
// 也就是这里的HandlerAdapter。由于前面获取handler的时候,不同的HandlerMapping
// 所产生的handler是不一样的,比如ReqeustMappingHandlerMapping产生的handler是一个
// HandlerMethod对象,因而这里在判断某个HandlerAdapter是否能够用于适配当前handler的
// 时候是通过其supports()方法进行的,比如RequestMappingHandlerAdapter就是判断
// 当前的handler是否为HandlerMethod类型,从而判断其是否能够用于适配当前handler。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
// 这里判断请求方式是否为GET或HEAD请求,如果是这两种请求的一种,那么就会判断
// 当前请求的资源是否超过了其lastModified时间,如果没超过,则直接返回,
// 并且告知浏览器可以直接使用缓存来处理当前请求
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request,
mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request)
+ "] is: " + lastModified);
}
if (new ServletWebRequest(request, response)
.checkNotModified(lastModified) && isGet) {
return;
}
}
// 这里在真正处理请求之前会获取容器中所有的拦截器,也就是HandlerInterceptor对象,
// 然后依次调用其preHandle()方法,如果某个preHandle()方法返回了false,那么就说明
// 当前请求无法通过拦截器的过滤,因而就会直接出发其afterCompletion()方法,只有在
// 所有的preHandle()方法都返回true时才会认为当前请求是能够使用目标handler进行处理的
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 在当前请求通过了所有拦截器的预处理之后,这里就直接调用HandlerAdapter.handle()
// 方法来处理当前请求,并且将处理结果封装为一个ModelAndView对象。该对象中主要有两个
// 属性:view和model,这里的view存储了后续需要展示的逻辑视图名或视图对象,而model
// 中则保存了用于渲染视图所需要的属性
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果当前是一个异步任务,那么就会释放当前线程,等待异步任务处理完成之后才将
// 任务的处理结果返回到页面
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 如果返回的ModelAndView对象中没有指定视图名或视图对象,那么就会根据当前请求的url
// 来生成一个视图名
applyDefaultViewName(processedRequest, mv);
// 在请求处理完成之后,依次调用拦截器的postHandle()方法,对请求进行后置处理
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
// 将处理请求过程中产生的异常封装到dispatchException中
dispatchException = new NestedServletException("Handler dispatch failed",
err);
}
// 这里主要是请求处理之后生成的视图进行渲染,也包括出现异常之后对异常的处理。
// 渲染完之后会依次调用拦截器的afterCompletion()方法来对请求进行最终处理
processDispatchResult(processedRequest, response, mappedHandler, mv,
dispatchException);
} catch (Exception ex) {
// 如果在上述过程中任意位置抛出异常,包括渲染视图时抛出异常,那么都会触发拦截器的
// afterCompletion()方法的调用
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
} finally {
// 如果当前异步任务已经开始,则触发异步任务拦截器的afterConcurrentHandlingStarted()方法
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest,
response);
}
} else {
// 如果当前是一个文件请求,则清理当前request中的文件数据
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
该方法是用户请求的重要方法,主要执行流程有:
下面我们再来细聊 doDispatch 中的几个正要流程
该方法分析过了,在 4.1 小结中
org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
// 遍历当前容器中所有的HandlerAdapter,通过调用其supports()方法,判断当前HandlerAdapter
// 能否用于适配当前的handler,如果可以,则直接使用该HandlerAdapter
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
}
// 如果找不到任何一个HandlerAdapter用于适配当前请求,则抛出异常
throw new ServletException("No adapter for handler [" + handler
+ "]: The DispatcherServlet configuration needs to include a HandlerAdapter"
+ " that supports this handler");
}
该方法会去遍历事先注册好的 HandlerAdapter 实现类,去调用实现类的 supports 方法判断是否支持当前的 handler实例的处理,一旦找到匹配的 HandlerAdapter 实现类,直接返回 HandlerAdapter 实例。
通过 DispatcherServlet.properties 默认配置文件可知,HandlerAdapter 默认有三种值:
这里,我们关注 RequestMappingHandlerAdapter 。
该类继承结构体
调用 supports 方法,会来到下面实现类:
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#supports
public final boolean supports(Object handler) {
// 判断当前handler是否为HandlerMethod类型,并且判断supportsInternal()方法返回值是否为true,
// 这里supportsInternal()方法是提供给子类实现的一个方法,对于RequestMappingHandlerAdapter
// 而言,其返回值始终是true,因为其只需要处理的handler是HandlerMethod类型的即可
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#supportsInternal
protected boolean supportsInternal(HandlerMethod handlerMethod) {
// 这里RequestMappingHandlerAdapter只是对supportsInternal()返回true,因为其只需要
// 处理的handler类型是HandlerMethod类型即可
return true;
}
org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 获取容器中所有拦截器
HandlerInterceptor[] interceptors = getInterceptors();
// 拦截器集合不为空
if (!ObjectUtils.isEmpty(interceptors)) {
// 遍历拦截器,调用 preHandle 方法
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
拦截器执行拦截,对客户端请求requset进行拦截,一旦某个HandlerInterceptor的preHandle方法返回false,则直接return,不在对请求在进行处理
该方法是处理具体的 Controller 方法的,其主要流程有以下几点:
@ModelAttribute
但是没标注 @RequestMapping
注解的方法,在真正调用具体的 handler 之前会将这些方法依次进行调用;@InitBinder
注解的方法,调用这些方法以对一些用户自定义的参数进行转换并且绑定;@RequestParam
,@ModelAttribute
等,获取其对应的 ArgumentResolver
,以将 request 中的参数转换为当前方法中对应注解的类型;ReturnValueHandler
对返回值进行适配,比如 ModelAndView
类型的返回值就由 ModelAndViewMethodReturnValueHandler
处理,最终将所有的处理结果都统一封装为一个 ModelAndView
类型的返回值,这也是 RequestMappingHandlerAdapter.handle()
方法的返回值类型。源码:
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// 检测当前请求,验证请求方法合法性和session合法性
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
// 根据synchronizeOnSession值判断当前是否需要支持在同一个session中只能线性地处理请求
if (this.synchronizeOnSession) {
// 获取当前请求的session对象
HttpSession session = request.getSession(false);
if (session != null) {
// 获取最佳互斥锁,即同步当前回话对象;如未能获取到互斥锁,将返回HttpSession对象本身
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
// 对HandlerMethod进行参数等的适配处理,并调用目标handler
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
// 即无最佳互斥锁,也未能获取到HttpSession,则当前会话无需串行化访问
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
// 如果当前不需要对session进行同步处理,则直接对HandlerMethod进行适配
mav = invokeHandlerMethod(request, response, handlerMethod);
}
// 相应信息不包含Cache-Control
// 判断当前请求头中是否包含Cache-Control请求头,如果不包含,则对当前response进行处理,
// 为其设置过期时间
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
// 如果当前SessionAttribute中存在配置的attributes,则为其设置过期时间。
// 这里SessionAttribute主要是通过@SessionAttribute注解生成的
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
// 如果当前不存在SessionAttributes,则判断当前是否存在Cache-Control设置,
// 如果存在,则按照该设置进行response处理,如果不存在,则设置response中的
// Cache的过期时间为-1,即立即失效
prepareResponse(response);
}
}
return mav;
}
该方法执行流程主要分为两部分
可以看到该方法的主要功能在 invokeHandlerMethod 方法,源码如下:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中
// 配置的InitBinder,用于进行参数的绑定
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
// 获取容器中全局配置的ModelAttribute和当前当前HandlerMethod所对应的Controller
// 中配置的ModelAttribute,这些配置的方法将会在目标方法调用之前进行调用
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 将handlerMethod封装为一个ServletInvocableHandlerMethod对象,
// 该对象用于对当前request的整体调用流程进行了封装
ServletInvocableHandlerMethod invocableMethod =
createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
// 设置当前容器中配置的所有ArgumentResolver
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
// 设置当前容器中配置的所有ReturnValueHandler
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 将前面创建的WebDataBinderFactory设置到ServletInvocableHandlerMethod中
invocableMethod.setDataBinderFactory(binderFactory);
// 设置ParameterNameDiscoverer,该对象将按照一定的规则获取当前参数的名称
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 创建ModelAndViewContainer,并初始化Model对象
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
// 这里initModel()方法主要作用是调用前面获取到的@ModelAttribute标注的方法,
// 从而达到@ModelAttribute标注的方法能够在目标Handler调用之前调用的目的
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// 获取当前的AsyncWebRequest,这里AsyncWebRequest的主要作用是用于判断目标
// handler的返回值是否为WebAsyncTask或DefferredResult,如果是这两种中的一种,
// 则说明当前请求的处理应该是异步的。所谓的异步,指的是当前请求会将Controller中
// 封装的业务逻辑放到一个线程池中进行调用,待该调用有返回结果之后再返回到response中。
// 这种处理的优点在于用于请求分发的线程能够解放出来,从而处理更多的请求,只有待目标任务
// 完成之后才会回来将该异步任务的结果返回。
AsyncWebRequest asyncWebRequest = WebAsyncUtils
.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
// 封装异步任务的线程池,request和interceptors到WebAsyncManager中
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();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
// 封装异步任务的处理结果,虽然封装的是一个HandlerMethod,但只是Spring简单的封装
// 的一个Callable对象,该对象中直接将调用结果返回了。这样封装的目的在于能够统一的
// 进行右面的ServletInvocableHandlerMethod.invokeAndHandle()方法的调用
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 对请求参数进行处理,调用目标HandlerMethod,并且将返回值封装为一个ModelAndView对象
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 对封装的ModelAndView进行处理,主要是判断当前请求是否进行了重定向,如果进行了重定向,
// 还会判断是否需要将FlashAttributes封装到新的请求中
return getModelAndView(mavContainer, modelFactory, webRequest);
} finally {
// 调用request destruction callbacks和对SessionAttributes进行处理
webRequest.requestCompleted();
}
}
该方法干了些啥:
@InitBinder
注解注册的属性转换器(扩展 @InitBinder )@ModelAttribute
标注但没有使用 @RequestMapping
标注的方法,并且在调用目标方法之前调用这些方法(扩展 @ModelAttribute)那么下面依次分析这几个主要步骤。
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDataBinderFactory
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod)
throws Exception {
// 判断当前缓存中是否缓存了当前bean所需要装配的InitBinder方法,如果存在,则直接从缓存中取,
// 如果不存在,则在当前bean中进行扫描获取
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.initBinderCache.get(handlerType);
if (methods == null) {
// 在当前bean中查找所有标注了@InitBinder注解的方法,这里INIT_BINDER_METHODS就是一个
// 选择器,表示只获取使用@InitBinder标注的方法
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.initBinderCache.put(handlerType, methods);
}
// 这里initBinderAdviceCache是在RequestMappingHandlerAdapter初始化时同步初始化的,
// 其内包含的方法有如下两个特点:①当前方法所在类使用@ControllerAdvice进行标注了;
// ②当前方法使用@InitBinder进行了标注。也就是说其内保存的方法可以理解为是全局类型
// 的参数绑定方法
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
// 这里判断的是当前配置的全局类型的InitBinder是否能够应用于当前bean,
// 判断的方式主要在@ControllerAdvice注解中进行了声明,包括通过包名,类所在的包,
// 接口或者注解的形式限定的范围
if (clazz.isApplicableToBeanType(handlerType)) {
Object bean = clazz.resolveBean();
for (Method method : methodSet) {
initBinderMethods.add(createInitBinderMethod(bean, method));
}
}
});
// 这里是将当前HandlerMethod所在bean中的InitBinder添加到需要执行的initBinderMethods中。
// 这里从添加的顺序可以看出,全局类型的InitBinder会在当前bean中的InitBinder之前执行
for (Method method : methods) {
Object bean = handlerMethod.getBean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
// 将需要执行的InitBinder封装到InitBinderDataBinderFactory中
return createDataBinderFactory(initBinderMethods);
}
这里获取 InitBinder 的方式主要有两种:
@ControllerAdvice
进行标注,并且声明方法上使用 @InitBinder
进行标注。@InitBinder
注解标注的方法。这两种方式,全局配置的会优先于局部配置的 InitBinder 执行。另外,标注该注解的方法执行时机只会在参数绑定时执行。
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelFactory
private ModelFactory getModelFactory(HandlerMethod handlerMethod,
WebDataBinderFactory binderFactory) {
// 这里SessionAttributeHandler的作用是声明几个属性,使其能够在多个请求之间共享,
// 并且其能够保证当前request返回的model中始终保有这些属性
SessionAttributesHandler sessionAttrHandler =
getSessionAttributesHandler(handlerMethod);
// 判断缓存中是否保存有当前handler执行之前所需要执行的标注了@ModelAttribute的方法
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.modelAttributeCache.get(handlerType);
if (methods == null) {
// 如果缓存中没有相关属性,那么就在当前bean中查找所有使用@ModelAttribute标注,但是
// 没有使用@RequestMapping标注的方法,并将这些方法缓存起来
methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
this.modelAttributeCache.put(handlerType, methods);
}
// 获取全局的使用@ModelAttribute标注,但是没有使用@RequestMapping标注的方法,
// 这里全局类型的方法的声明方式需要注意的是,其所在的bean必须使用@ControllerAdvice进行标注
List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
// 判断@ControllerAdvice中指定的作用的bean范围与当前bean是否匹配,匹配了才会对其应用
if (clazz.isApplicableToBeanType(handlerType)) {
Object bean = clazz.resolveBean();
for (Method method : methodSet) {
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
}
});
// 将当前方法中使用@ModelAttribute标注的方法添加到需要执行的attrMethods中。从这里的添加顺序
// 可以看出,全局类型的方法将会先于局部类型的方法执行
for (Method method : methods) {
Object bean = handlerMethod.getBean();
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
// 将需要执行的方法等数据封装为ModelFactory对象
return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}
该方法获取 @ModelAttribute 的流程和 getDataBinderFactory 方法非常详细,大家可以类比去看看。
上面 getModelFactory 获取到了 ModelAttribute 注解标注的方法,那么来看看这些标注的方法是如何执行的,源码如下:
org.springframework.web.method.annotation.ModelFactory#initModel
public void initModel(NativeWebRequest request, ModelAndViewContainer container,
HandlerMethod handlerMethod) throws Exception {
// 在当前request中获取使用@SessionAttribute注解声明的参数
Map<String, ?> sessionAttributes =
this.sessionAttributesHandler.retrieveAttributes(request);
// 将@SessionAttribute声明的参数封装到ModelAndViewContainer中
container.mergeAttributes(sessionAttributes);
// 调用前面获取的使用@ModelAttribute标注的方法
invokeModelAttributeMethods(request, container);
// 这里首先获取目标handler执行所需的参数中与@SessionAttribute同名或同类型的参数,
// 也就是handler想要直接从@SessionAttribute中声明的参数中获取的参数。然后对这些参数
// 进行遍历,首先判断request中是否包含该属性,如果不包含,则从之前的SessionAttribute缓存
// 中获取,如果两个都没有,则直接抛出异常
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!container.containsAttribute(name)) {
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '"
+ name + "'", name);
}
container.addAttribute(name, value);
}
}
}
该方法主要做了两件事情:
@ModelAttribute
标注的方法(invokeModelAttributeMethods )下面来看看 invokeModelAttributeMethods 方法源码:
org.springframework.web.method.annotation.ModelFactory#invokeModelAttributeMethods
private void invokeModelAttributeMethods(NativeWebRequest request,
ModelAndViewContainer container) throws Exception {
while (!this.modelMethods.isEmpty()) {
// 这里getNextModelMethod()方法始终会获取modelMethods中的第0号为的方法,
// 后续该方法执行完了之后则会将该方法从modelMethods移除掉,因而这里while
// 循环只需要判断modelMethods是否为空即可
InvocableHandlerMethod modelMethod =
getNextModelMethod(container).getHandlerMethod();
// 获取当前方法中标注的ModelAttribute属性,然后判断当前request中是否有与该属性中name字段
// 标注的值相同的属性,如果存在,并且当前ModelAttribute设置了不对该属性进行绑定,那么
// 就直接略过当前方法的执行
ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
Assert.state(ann != null, "No ModelAttribute annotation");
if (container.containsAttribute(ann.name())) {
if (!ann.binding()) {
container.setBindingDisabled(ann.name());
}
continue;
}
// 通过ArgumentResolver对方法参数进行处理,并且调用目标方法
Object returnValue = modelMethod.invokeForRequest(request, container);
// 如果当前方法的返回值不为空,则判断当前@ModelAttribute是否设置了需要绑定返回值,
// 如果设置了,则将返回值绑定到请求中,后续handler可以直接使用该参数
if (!modelMethod.isVoid()){
String returnValueName = getNameForReturnValue(returnValue,
modelMethod.getReturnType());
if (!ann.binding()) {
container.setBindingDisabled(returnValueName);
}
// 如果request中不包含该参数,则将该返回值添加到ModelAndViewContainer中,
// 供handler使用
if (!container.containsAttribute(returnValueName)) {
container.addAttribute(returnValueName, returnValue);
}
}
}
}
这里调用使用 @ModelAttribute
标注的方法的方式比较简单,主要需要注意的是,对于调用结果,如果当前 request 中没有同名的参数,则会将调用结果添加到 ModelAndViewContainer 中,以供给后续 handler 使用。
在进行了相关前置方法调用和异步任务的判断之后,RequestMappingHandlerAdapter 就会开始调用目标 handler 了。调用过程在 ServletInvocableHandlerMethod.invokeAndHandle()
方法中,如下是该方法的源码:
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 对目标handler的参数进行处理,并且调用目标handler
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// 设置相关的返回状态
setResponseStatus(webRequest);
// 如果请求处理完成,则设置requestHandled属性
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null
|| mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(getResponseStatusReason())) {
// 如果请求失败,但是有错误原因,那么也会设置requestHandled属性
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 遍历当前容器中所有ReturnValueHandler,判断哪种handler支持当前返回值的处理,
// 如果支持,则使用该handler处理该返回值
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value",
returnValue), ex);
}
throw ex;
}
}
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
// 获取HttpStatus
HttpStatus status = getResponseStatus();
// 未发现HttpStatus直接返回
if (status == null) {
return;
}
HttpServletResponse response = webRequest.getResponse();
if (response != null) {
String reason = getResponseStatusReason();
if (StringUtils.hasText(reason)) {
/**
* 注意 注意 注意:这里是 sendError , 不是 setError
* 使用指定的状态码并清空缓冲,发送一个错误响应至客户端。如果响应已经被提交,这个方法会抛出IllegalStateException。
* 服务器默认会创建一个HTML格式的服务错误页面作为响应结果,其中包含参数msg指定的文本信息,
* 这个HTML页面的内容类型为“text/html”,保留cookies和其他未修改的响应头信息。
*
* 如果一个对应于传入的错误码的错误页面已经在web.xml中声明,那么这个声明的错误页面将会优先于建议的msg参数服务于客户端。
*/
response.sendError(status.value(), reason);
}
else {
/**
* 设置响应的状态码。
* 这个方法被用于当响应结果正常时(例如,状态码为SC_OK或SC_MOVED_TEMPORARTLY)设置响应状态码。
* 如果发生错误,而且来访者希望调用在web应用中定义的错误页面作为显示,那么应该使用sendError方法代替之。
* 使用setStatus方法之后,容器会清空缓冲并设置Location响应头,保留cookies和其他响应头信息。
*/
response.setStatus(status.value());
}
}
// To be picked up by RedirectView
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}
该方法主要分为三个步骤
下面来看看第一个步骤 invokeForRequest 方法源码
org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
public Object invokeForRequest(NativeWebRequest request, @Nullable
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 将request中的参数转换为当前handler的参数形式
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(),
getBeanType()) + "' with arguments " + Arrays.toString(args));
}
// 这里doInvoke()方法主要是结合处理后的参数,使用反射对目标方法进行调用
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(),
getBeanType()) + "] returned [" + returnValue + "]");
}
return returnValue;
}
org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
// 本方法主要是通过当前容器中配置的ArgumentResolver对request中的参数进行转化,
// 将其处理为目标handler的参数的形式
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 获取当前handler所声明的所有参数,主要包括参数名,参数类型,参数位置,所标注的注解等等属性
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// providedArgs是调用方提供的参数,这里主要是判断这些参数中是否有当前类型
// 或其子类型的参数,如果有,则直接使用调用方提供的参数,对于请求处理而言,默认情况下,
// 调用方提供的参数都是长度为0的数组
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 如果在调用方提供的参数中不能找到当前类型的参数值,则遍历Spring容器中所有的
// ArgumentResolver,判断哪种类型的Resolver支持对当前参数的解析,这里的判断
// 方式比较简单,比如RequestParamMethodArgumentResolver就是判断当前参数
// 是否使用@RequestParam注解进行了标注
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
// 如果能够找到对当前参数进行处理的ArgumentResolver,则调用其
// resolveArgument()方法从request中获取对应的参数值,并且进行转换
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
} catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Failed to resolve",
i), ex);
}
throw ex;
}
}
// 如果进行了参数处理之后当前参数还是为空,则抛出异常
if (args[i] == null) {
throw new IllegalStateException("Could not resolve method parameter at index "
+ parameter.getParameterIndex() + " in "
+ parameter.getExecutable().toGenericString()
+ ": " + getArgumentResolutionErrorMessage("No suitable resolver for",i));
}
}
return args;
}
关于 handler 的调用,可以看到,这里的实现也是比较简单的,首先是遍历所有的参数,并且查找哪种 ArgumentResolver 能够处理当前参数,找到了则按照具体的 Resolver 定义的方式进行处理即可。在所有的参数处理完成之后,RequestMappingHandlerAdapter 就会使用反射调用目标 handler。
再来看看第二个步骤 setResponseStatus 方法,源码如下:
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#setResponseStatus
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
// 获取HttpStatus
HttpStatus status = getResponseStatus();
// 未发现HttpStatus直接返回
if (status == null) {
return;
}
HttpServletResponse response = webRequest.getResponse();
if (response != null) {
String reason = getResponseStatusReason();
if (StringUtils.hasText(reason)) {
/**
* 注意 注意 注意:这里是 sendError , 不是 setError
* 使用指定的状态码并清空缓冲,发送一个错误响应至客户端。如果响应已经被提交,这个方法会抛出IllegalStateException。
* 服务器默认会创建一个HTML格式的服务错误页面作为响应结果,其中包含参数msg指定的文本信息,
* 这个HTML页面的内容类型为“text/html”,保留cookies和其他未修改的响应头信息。
*
* 如果一个对应于传入的错误码的错误页面已经在web.xml中声明,那么这个声明的错误页面将会优先于建议的msg参数服务于客户端。
*/
response.sendError(status.value(), reason);
}
else {
/**
* 设置响应的状态码。
* 这个方法被用于当响应结果正常时(例如,状态码为SC_OK或SC_MOVED_TEMPORARTLY)设置响应状态码。
* 如果发生错误,而且来访者希望调用在web应用中定义的错误页面作为显示,那么应该使用sendError方法代替之。
* 使用setStatus方法之后,容器会清空缓冲并设置Location响应头,保留cookies和其他响应头信息。
*/
response.setStatus(status.value());
}
}
// To be picked up by RedirectView
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}
最后来看第三个步骤 handleReturnValue 方法,源码如下:
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 获取能够处理当前返回值的Handler,比如如果返回值是ModelAndView类型,那么这里的handler就是
// ModelAndViewMethodReturnValueHandler
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: "
+ returnType.getParameterType().getName());
}
// 通过获取到的handler处理返回值,并将其封装到ModelAndViewContainer中
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
该方法也非常简单,主要就是两个步骤:
下面来看看如何获取处理返回值得 Handler 方法源码:
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler
// 本方法的主要作用是获取能够处理当前返回值的ReturnValueHandler
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value,
MethodParameter returnType) {
// 判断返回值是否为异步类型的返回值,即WebAsyncTask或DefferredResult
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
// 对所有的ReturnValueHandler进行遍历,判断其是否支持当前返回值的处理。这里如果当前返回值
// 是异步类型的返回值,还会判断当前ReturnValueHandler是否为
// AsyncHandlerMethodReturnValueHandler类型,如果不是,则会继续查找
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
// 判断是否支持返回值处理的主要位置,比如ModelAndViewMethodReturnValueHandler就会
// 判断返回值是否为ModelAndView类型,如果是,则表示其是当前ReturnValuleHandler所支持的类型
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
对于具体处理返回值的逻辑,每个 ReturnValueHandler 各不相同,这里不做分析。
构建 ModelAndView 对象,通过 Model 和 View 。
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelAndView
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
// 更新模型
// 将列为@SessionAttributes的模型属性提升到会话
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
// 获取ModelMap并创建ModelAndView
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
// 处理引用类型视图和转发类型视图
// 真正的View 可见ModelMap/视图名称、状态HttpStatus最终都交给了Veiw去渲染
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
// 这个步骤:是Spring MVC对重定向的支持
// 重定向之间传值,使用的RedirectAttributes这种Model
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
执行处理生成默认视图名,也就是添加前缀和后缀等。
org.springframework.web.servlet.DispatcherServlet#applyDefaultViewName
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
// ModelAndView 不为空并且 ModelAndView 中不包含视图
if (mv != null && !mv.hasView()) {
// 获取默认视图名称
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
// 给 ModelAndView 设置视图名称
mv.setViewName(defaultViewName);
}
}
}
可以看到,这里的判断逻辑很简单,首先检查 mv 是否为 null(如果用户添加了 @ResponseBody
注解,mv 就为 null),然后去判断 mv 中是否包含视图,如果不包含视图,则调用 getDefaultViewName 方法去获取默认的视图名,并将获取到的默认视图名交给 mv。
org.springframework.web.servlet.DispatcherServlet#getDefaultViewName
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
// 如果默认视图解析器不为空则调用 getViewName 方法获取默认视图名称,反之返回 null
return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
}
这里涉及到一个新的组件 viewNameTranslator,如果 viewNameTranslator 不为 null,则调用其 getViewName 方法获取默认的视图名。
org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#getViewName
public String getViewName(HttpServletRequest request) {
// 获取 request 中的请求路径获取
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
// 拼接默认视图名称: 前缀 + transformPath 方法返回值 + 后缀
return (this.prefix + transformPath(lookupPath) + this.suffix);
}
在 getViewName 方法中,首先提取出来当前请求路径,如果请求地址是 http://localhost:8080/test
,那么这里提取出来的路径就是 /test
,然后通过 transformPath 方法对路径进行处理,再分别加上前后缀后返回,默认的前后缀都是空字符串(如有需要,也可以自行配置)。
org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#transformPath
protected String transformPath(String lookupPath) {
String path = lookupPath;
if (this.stripLeadingSlash && path.startsWith(SLASH)) {
path = path.substring(1);
}
if (this.stripTrailingSlash && path.endsWith(SLASH)) {
path = path.substring(0, path.length() - 1);
}
if (this.stripExtension) {
path = StringUtils.stripFilenameExtension(path);
}
if (!SLASH.equals(this.separator)) {
path = StringUtils.replace(path, SLASH, this.separator);
}
return path;
}
transformPath 则主要干了如下几件事:
/
。/
。/test.abc
,经过这一步处理后,就变成了 /test
。好了,经过这一波处理后,正常情况下,我们就拿到了一个新的视图名,这个新的视图名就是你的请求路径。
例如请求路径是 http://localhost:8080/test.abc
,那么获取到的默认视图名就是 test
。
该方法是应用所有拦截器的 postHandle 方法。
org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
// 获取容器中拦截器集合
HandlerInterceptor[] interceptors = getInterceptors();
// 不为空,则循环遍历拦截器并执行其 postHandle 方法
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
此方法是处理最终结果的,包括异常处理、渲染页面和发出完成通知触发拦截器的afterCompletion()方法执行等
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
private void processDispatchResult(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler,
@Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
// 用于标记当前生成view是否是异常处理之后生成的view
boolean errorView = false;
if (exception != null) {
// 如果当前的异常是ModelAndViewDefiningException类型,则说明是ModelAndView的定义
// 异常,那么就会调用其getModelAndView()方法生成一个新的view
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
} else {
// 如果生成的异常是其他类型的异常,就会在当前容器中查找能够处理当前异常的“拦截器”,
// 找到之后调用这些拦截器,然后生成一个新的ModelAndView
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 如果得到的ModelAndView对象(无论是否为异常处理之后生成的ModelAndView)不为空,并且没有被清理,
// 那么就会对其进行渲染,渲染的主要逻辑在render()方法中
if (mv != null && !mv.wasCleared()) {
// 该方法主要有两个功能:根据view名称封装view视图对象(view对象的构建) 和 渲染数据(将modelMap中的数据暴露到request域中)
render(mv, request, response);
if (errorView) {
// 如果当前是异常处理之后生成的视图,那么就请求当前request中与异常相关的属性
WebUtils.clearErrorRequestAttributes(request);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '"
+ getServletName() + "': assuming HandlerAdapter completed request "
+ "handling");
}
}
// 如果当前正在进行异步请求任务的调用,则直接释放当前线程,等异步任务处理完之后再进行处理
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return;
}
// 在视图渲染完成之后,依次调用当前容器中所有拦截器的afterCompletion()方法
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
SpringMVC视图解析配置
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
bean>
最后画图小能手上线,SpringMVC请求流程图如下:
至此,我们的 SpringMVC 的源码分析到此就结束了。
好了,今天的内容到这里就结束了,我是 【J3】关注我,我们下期见
。
由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。
如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。
感谢您的阅读,十分欢迎并感谢您的关注。