一、九大组件
- HandlerMapping(处理器映射器)
HandlerMapping 是⽤来查找Handler的,也就是处理器,具体的表现形式可以是类,也可以是⽅法
。⽐如,标注了@RequestMapping的每个⽅法都可以看成是⼀个Handler
。Handler负责具 体实际的请求处理,在请求到达后,HandlerMapping 的作⽤便是找到请求相应的处理器Handler 和 Interceptor.
- HandlerAdapter(处理器适配器)
HandlerAdapter 是⼀个适配器。因为 SpringMVC 中 Handler 可以是任意形式的,只要能处理请求即可
。但是把请求交给 Servlet 的时候,由于 Servlet 的⽅法结构都是doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理⽅法调⽤ Handler 来进⾏处理,便是 HandlerAdapter 的职责。
注意:因为RequestMappingHandlerAdapter实现了 InitializingBean接口
,所以在其初始化之后,会调用afterPropertiesSet()。在该方法中对spring默认的方法参数解析器,方法返回值解析器,初始化绑定器进行了设置的
- 参数解析器
- 返回值处理器
- HandlerExceptionResolver
HandlerExceptionResolver ⽤于处理 Handler 产⽣的异常情况。它的作⽤是根据异常设置 ModelAndView,之后交给渲染⽅法进⾏渲染
,渲染⽅法会将 ModelAndView 渲染成⻚⾯。
- ViewResolver
ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图
,只有⼀ 个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名viewName 最终会在这⾥被解析成为View
。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回的参数和数据填⼊模板中,⽣成html⽂件
。ViewResolver在这个过程主要完成两件事情: ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到视图的类型,如JSP)并填⼊参数。
默认情况下,Spring MVC会⾃动为我们配置⼀个InternalResourceViewResolver,是针对 JSP 类型视图的。
- RequestToViewNameTranslator
RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,便要通过这个组件从请求中查找 ViewName。
- LocaleResolver
ViewResolver组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这个组件也是 i18n 的基础。
- ThemeResolver
ThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。 Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图⽚、CSS样式等
。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名, ThemeSource根据主题名找到具体的主题,其抽象也就是Theme
,可以通过Theme来获取主题和具体的资源。
- MultipartResolver
MultipartResolver ⽤于上传请求,通过将普通的请求包装成MultipartHttpServletRequest 来实现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。
如果上传多个⽂件,还可以调⽤ getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就是封装普通的请求,使其拥有⽂件上传的功能。
- FlashMapManager
FlashMap ⽤于重定向时的参数传递
,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然 可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得呢?因为重定向没有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通 过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过 ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯上就可以直接从Model中获取数据。
FlashMapManager 就是⽤来管理 FalshMap 的。
二、SpringMVC的一次请求
我们先来看一下入口在哪。众所周知,Servlet标准定义了所有请求先由service方法处理,如果是get或post方法,那么再交由doGet或是doPost方法处理
。
FrameworkServlet覆盖了service方法:
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
processRequest(request, response);
} else {
super.service(request, response);
}
}
Spring要覆盖此方法的目的在于拦截PATCH请求,PATCH请求与PUT类似,不同在于PATCH是局部更新,而后者是全部更新
。FrameworkServlet同样也覆盖了doGet和doPost方法,两者只是调用processRequest方法。
- 请求上下文
- processRequest方法
它首先备份了请求的LocaleContext和RequestAttributes
,然后copy了一份新的,并绑定到当前线程
:
//将地区(Locale)和请求属性以ThreadLocal的方法与当前线程进行关联,分别可以通过LocaleContextHolder和RequestContextHolder进行获取。
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
initContextHolders就是将localeContext和requestAttributes两个对象存入Holder。并且在方法最后,将备份的数据恢复过来,并触发请求处理完毕事件:
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
备份和绑定操作完成后,调用它的核心方法doService,这是一个抽象方法,在DispatcherServlet实现,首先也是备份属性,并且最后也进行了恢复
:
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里面可以随意获取
// doService将webApplication、localeResolver、themeResolver、ThemeSource、outputFlashMap和flashMapManage设置到request的属性中,
// 请求分发。
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 {
//然后将请求传入到doDispatch中,DispatcherServlet最重要的方法,交给他去分发请求、找到handler处理等等
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
- DispatcherServlet.doDispatch源码:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 此处用processedRequest 需要注意的是:若是处理上传,processedRequest 将和request不再指向同一对象
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
//主要用来管理异步请求的处理。什么时候要用到异步处理呢?就是业务逻辑复杂(或者其他原因),为了避免请求线程阻塞,需要委托给另一个线程的时候。
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//检查是否为上传文件请求
//checkMultipart 这个方法很重要,判断是否是上传需求。且看下面的具体分析:
//如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求
processedRequest = checkMultipart(request);
//标记一下:是否是文件上传的request
multipartRequestParsed = (processedRequest != request);
// 步骤1,获取执行链,重要(也就是找到某个controller中的某个方法)
// 找到一个处理器,如果没有找到对应的处理类的话,这里通常会返回404,如果throwExceptionIfNoHandlerFound属性值为true的情况下会抛出异常
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 步骤2,获取适配器,一般都是返回RequestMappingHandlerAdapter(用于处理@RequestMapping注解的方法)
// 根据实际的handler去找到一个合适的HandlerAdapter,方法详细逻辑同getHandler,因此不再解释
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//如果是GET请求,如果内容没有变化的话,则直接返回,用于响应304状态码
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
//在客户端地一次输入URL时,服务器端会返回内容和状态码200, 表示请求成功,同时会添加一个“Last-Modified”属性,表示该请求资源的最后修改时间
//客户端第二次请求此URL时,客户端会向服务器发送请求头 “IF-Modified-Since”,
//如果服务端内容没有变化,则自动返回HTTP304状态码(只返回相应头信息,不返回资源文件内容,这样就可以节省网络带宽,提供响应速度和用户体验)
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 步骤3,拦截器pre方法,重要
// 这段代码很有意思:执行处理器连里的拦截器们,具体参阅下面详细:
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//步骤4,执行目标方法,真正处理逻辑,重要
// 真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndView
// 这也是一个很复杂的过程(序列化、数据绑定等等),需要后面专题讲解
// 这里一般最后都是由RequestMappingHandlerAdapter的invocableMethod.invokeHandle执行
// 参数解析和返回值处理都在这个方法执行
// 如果是@ResponseBody的方法,那么mv=null
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//意思是:如果我们没有设置viewName,就采用默认的。否则采用我们自己的
applyDefaultViewName(processedRequest, mv);
//步骤5,拦截器post方法,重要
// 执行所有的拦截器的postHandle方法,并且把mv给他
// 这里有一个小细节:这个时候拦截器是【倒序】执行的
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//步骤6,处理视图,重要
//这个方法很重要,顾名思义,他是来处理结果的,渲染视图、处理异常等等的 下面详细分解
//如果是@ResponseBody,那么这个方法里面的逻辑除了拦截器的逻辑,其他都不会执行
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//步骤7,拦截器收尾方法,重要
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
- checkMultipart
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
// 只有配置了multipartResolver,并且是文件上传的请求,才会继续往下走,没有的话就可以不必继续了。
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
// 如果是文件上传请求,则继续判断这个请求是不是已经被转换为MultipartHttpServletRequest类型了
// 如果该请求已经是MultipartHttpServletRequest 那就输出一个日志走人
//在Spring-Web这个jar中有一个过滤器org.springframework.web.multipart.support.MultipartFilter
//如果在web.xml中配置这个过滤器的话,则会在过滤器中提前判断是不是文件上传的请求,
//并将请求转换为MultipartHttpServletRequest类型。
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");
}
else {
try {
// 这里特别注意,不管是哪种multipartResolver的实现,内部都是new了一个新的MultipartHttpServletRequest的实现类,
// 所以不再指向原来的request了,所以一定要注意
// 将请求转换为MultipartHttpServletRequest类型
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);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
// 如果前面没有返回,就原样返回,相当于啥都不做
return request;
}
- 处理器查找:
为此请求返回HandlerExecutionChain
即为请求寻找合适的Controller的过程
。DispatcherServlet.getHandler:
protected HandlerExecutionChain getHandler(HttpServletRequest request) {
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
从这里可以看出,寻找处理器实际上委托给HandlerMapping实现,寻找的过程便是遍历所有的HandlerMapping进行查找,一旦找到,那么不再继续进行遍历。
也就是说HandlerMapping之间有优先级的概念,而根据AnnotationDrivenBeanDefinitionParser的注释,RequestMappingHandlerMapping其实有最高的优先级。
AbstractHandlerMapping.getHandler:
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
//判断请求头中是否有ORIGIN字段
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ?
globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
getHandlerInternal方法便是根据url进行查找的过程。下面重点是执行链的生成。getHandlerExecutionChain方法的原理就是从adaptedInterceptors中获得所有可以适配当前请求URL的MappedInterceptor并将其添加到HandlerExecutionChain的拦截器列表中
。拦截器的顺序其实就是我们定义/注册的顺序。从getCorsHandlerExecutionChain的源码中可以看出,对于跨域请求其实是向调用链插入了一个CorsInterceptor。
- 适配器查找:
一般都是返回RequestMappingHandlerAdapter(用于处理@RequestMapping注解的方法)
,根据实际的handler去找到一个合适的HandlerAdapter,方法详细逻辑同getHandler
- DispatcherServlet.getHandlerAdapter:
protected HandlerAdapter getHandlerAdapter(Object handler) {
for (HandlerAdapter ha : this.handlerAdapters) {
if (ha.supports(handler)) {
return ha;
}
}
}
第一个适配器是RequestMappingHandlerAdapter,而其support方法直接返回true,这就导致了使用的适配器总是这一个
。
- 更多适配器
- SimpleControllerHandlerAdapter:适配SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping的映射的,也就是实现Controller接口的Handler
- AbstractHandlerMethodAdapter:
适配RequestMappingHandlerMapping,也就是我们常用的RequestMapping注解
- HttpRequestHandlerAdapter :适配远程调用的
- SimpleServletHandlerAdapter:适配Servlet实现类的
- supports
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
- supportsInternal
//总是返回true ,因为任何方法参数和返回值类型会以某种方式加以处理。
//不被任何HandlerMethodArgumentResolver识别的方法参数被解释为一个请求参数,如果它是一个简单的类型,或者作为模型属性否则。
//没有任何HandlerMethodReturnValueHandler识别的返回值将被解释为一个模型属性
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
如果是GET请求,内容没有变化则直接返回
//如果是GET请求,如果内容没有变化的话,则直接返回,用于响应304状态码
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
//在客户端地一次输入URL时,服务器端会返回内容和状态码200, 表示请求成功,同时会添加一个“Last-Modified”属性,表示该请求资源的最后修改时间
//客户端第二次请求此URL时,客户端会向服务器发送请求头 “IF-Modified-Since”,
//如果服务端内容没有变化,则自动返回HTTP304状态码(只返回相应头信息,不返回资源文件内容,这样就可以节省网络带宽,提供响应速度和用户体验)
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
- applyPreHandle
// 这段代码很有意思:执行处理器连里的拦截器们,具体参阅下面详细:
// 在Servlet规范中,设计了filter组件,可以在每个Web请求前后对它做处理,显然这种处理粒度太粗。Spring MVC增加了拦截器的概念,从HandlerMapping初始化和Handler查找的过程中,我们可以看到它的身影。
// 拦截器接口HandlerInterceptor定义了三个方法:preHandle、postHandle、afterCompletion,分别作用于处理器方法调用前后、处理器执行链全部执行后。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;}
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];
// 注意:如果是拦截器返回了false,就立马触发所有拦截器的AfterCompletion 方法。并且马上return false
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
- 请求处理:mv=ha.handle()
//步骤4,执行目标方法,真正处理逻辑,重要
// 真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndView
// 这也是一个很复杂的过程(序列化、数据绑定等等),需要后面专题讲解
// 这里一般最后都是由RequestMappingHandlerAdapter的invocableMethod.invokeHandle执行
// 参数解析和返回值处理都在这个方法执行
// 并返回一个ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的
if (asyncManager.isConcurrentHandlingStarted()) {
return;}
- 以处理
有@PathVariable注解的方法为例
因为在 getHandlerAdapter中已经返回了一个适配器RequestMappingHandlerAdapter,
那么其调用链如下:
- mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
- RequestMappingHandlerAdapter.handerInternal
- RequestMappingHandlerAdapter.invokeHanderMethod
- ServletInvocableHandlerMethod.invokeAndHandle
- ServletInvocableHandlerMethod.invokeForRequest
- InvocableHandlerMethod.invokeForRequest
- InvocableHandlerMethod.getMethodArgumentValues
- InvocableHandlerMethod.resolveArgument
- HandlerMethodArgumentResolverComposite.resolveArgument
- AbstractNameedValueMethodArgumentResolver.resolveName
- PathVariableMethodArgumentResolver.resolveName (如果解析的是@PathVariable注解的参数)
- RequestMappingHandlerAdapter.handleInternal:
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod){
ModelAndView mav;
// Execute invokeHandlerMethod in synchronized block if required.
// Session同步:可以看出,如果开启了synchronizeOnSession,那么同一个session的请求将会串行执行,
//这一选项默认是关闭的,当然我们可以通过注入的方式进行改变。
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
总之,在mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
中,会将方法参数进行解析,执行该方法,并封装一个ModelAndView返回
这篇文章清楚说明了,因为RequestMappingHandlerAdapter实现了 InitializingBean接口
,所以在其初始化之后,会调用afterPropertiesSet()。
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
//首先执行此操作,它可能会添加 ResponseBody 建议 bean
initControllerAdviceCache();
if (this.argumentResolvers == null) {
//初始化SpringMVC默认的方法参数解析器,并添加至argumentResolvers(HandlerMethodArgumentResolverComposite)
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
//初始化SpringMVC默认的初始化绑定器(@InitBinder)参数解析器,并添加至initBinderArgumentResolvers(HandlerMethodArgumentResolverComposite)
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
//获取默认的方法返回值解析器
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
进一步的,我们可以看到spring默认的方法参数解析器,方法返回值解析器,初始化绑定器都是在这个方法中设置的
-
同时我们也知道,HandlerAdapter内部含有一组解析器负责对各类型的参数进行解析(策略模式)。下面我们就
再以
常用的自定义参数和Model
为例进行说明。 -
- 自定义参数
解析由RequestParamMethodArgumentResolver
完成。
supportsParameter方法决定了一个解析器可以解析的参数类型,该解析器支持@RequestParam标准的参数或是简单类型的参数
,具体参见其注释。为什么此解析器可以同时解析@RequestParam注解和普通参数呢?玄机在于RequestMappingHandlerAdapter方法在初始化参数解析器时其实初始化了两个RequestMappingHandlerAdapter对象
,getDefaultArgumentResolvers方法相关源码:
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
}
useDefaultResolution参数用于启动对常规类型参数的解析,这里的常规类型指的又是什么呢?
实际上由BeanUtils.isSimpleProperty方法决定:
public static boolean isSimpleProperty(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
}
public static boolean isSimpleValueType(Class<?> clazz) {
return (ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() ||
CharSequence.class.isAssignableFrom(clazz) ||
Number.class.isAssignableFrom(clazz) ||
Date.class.isAssignableFrom(clazz) ||
URI.class == clazz || URL.class == clazz ||
Locale.class == clazz || Class.class == clazz);
}
忽略复杂的调用关系,最核心的实现位于resolveName方法,部分源码:
@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {
if (arg == null) {
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
name就是方法的参数名,可以看出,参数解析就是根据参数名去request查找对应属性的过程
,在这里参数类型并没有起什么作用。参数名是从哪里来的?
方法名获取的入口位于RequestParamMethodArgumentResolver的resolveArgument方法:
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
}
getNamedValueInfo方法最终完成对MethodParameter的getParameterName方法的调用:
public String getParameterName() {
ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;
if (discoverer != null) {
String[] parameterNames = (this.method != null ?
discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor));
if (parameterNames != null) {
this.parameterName = parameterNames[this.parameterIndex];
}
this.parameterNameDiscoverer = null;
}
return this.parameterName;
}
显然,参数名的获取由接口ParameterNameDiscoverer完成:
默认采用DefaultParameterNameDiscoverer,但此类其实相当于StandardReflectionParameterNameDiscoverer和LocalVariableTableParameterNameDiscoverer
的组合,且前者先于后者进行解析。
- StandardReflectionParameterNameDiscoverer.getParameterNames:
@Override
public String[] getParameterNames(Method method) {
Parameter[] parameters = method.getParameters();
String[] parameterNames = new String[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
if (!param.isNamePresent()) {
return null;
}
parameterNames[i] = param.getName();
}
return parameterNames;
}
-
- Model型参数解析
解析由ModelMethodProcessor完成。supportsParameter方法很简单:
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
很直白了。resolveArgument:
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return mavContainer.getModel();
}
忽略各种调用关系,Model其实是一个BindingAwareModelMap对象,且每次请求(需要注入Model的前提下)都有一个新的该对象生成
。类图:
- 参数解析总结
- 我们可以
通过实现HandlerMethodArgumentResolver接口并将其注册容器的方式实现自定义参数类型的解析
。- 为了防止出现参数名获取不到的问题,
应优先使用@RequestParam注解直接声明需要的参数名称。
- 返回值解析
返回值解析开始于ServletInvocableHandlerMethod.invokeAndHandle.invokeForRequest之后
//调用方法并通过其中一个处理返回值
//ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//与@ResponseStatus相关的处理
setResponseStatus(webRequest);
//进行返回值处理
if (returnValue == null) {
// Request的NotModified为true有@ResponseStatus注解标注 RequestHandled=true 三个条件有一个成立,则设置请求处理完成并返回
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
// 设置该请求已被处理
mavContainer.setRequestHandled(true);
return;
}
}
//返回值不为null,@ResponseStatus存在reason 同样设置请求处理完成并返回
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
// 前边都不成立,则设置RequestHandled=false即请求未完成
// 继续交给HandlerMethodReturnValueHandlerComposite处理
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
//利用所有返回值处理器来处理返回值
try {
//处理返回值
this.returnValueHandlers.handleReturnValue(
returnValue,
//获取返回值类型,并传入到方法中
getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
- 以标注了@ResponseBody的场景为例子
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//利用返回值,和返回值类型,选择适合的返回值处理器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
//利用得到的返回值处理器对返回值进行处理
//HandlerMethodReturnValueHandler.handleReturnValue
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
//先判断是不是异步返回值,遍历循环找到AsyncHandlerMethodReturnValueHandler进行判断
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
//遍历returnValueHandlers
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
//判断当前HandlerMethodReturnValueHandler是否能够支持当前返回值的解析
//HandlerMethodReturnValueHandler.supportsReturnType
//例子:如果标了@ResponseBody,那么利用RequestResponseBodyMethodProcessor进行解析
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
- 进入
RequestResponseBodyMethodProcessor
进行处理
//RequestResponseBodyMethodProcessor
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// 利用消息转换器来进行处理
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
这里又涉及到一个知识点,消息转换器Converter:通俗的理解就是看能不能将Class对象转为MediaType类型的数据(包括写入写出)
。这里只展示部分实现类:
了解更多请参考
而在应该消息转换器前,
还会有一步与浏览器与服务器内容协商的过程
,比如浏览器能接受json类型的,服务器也能接受json类型的,那么就可以以json的类型发送数据,然后应用消息转换器,将返回值转成json数据
- writeWithMessageConverters
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
//用于接收Controller返回值
Object body;
//声明接收返回对象类型
Class<?> valueType;
Type targetType;
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
}
else {
body = value;
valueType = getReturnValueType(body, returnType);
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
// 选择使用的 MediaType
MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType != null && contentType.isConcrete()) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
}
else {
HttpServletRequest request = inputMessage.getServletRequest();
// 获取浏览器能接受的MediaType(Accept-Type)
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 服务器能产生的MediaType(即@RequeMapping中指定的produces)
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
//声明匹配上的MediaTypes
List<MediaType> mediaTypesToUse = new ArrayList<>();
//循环匹配,协商MediaType
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
//没匹配上MediaTypes就报错
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using '" + selectedMediaType + "', given " +
acceptableTypes + " and supported " + producibleTypes);
}
}
//如果匹配到,则进行写入逻辑
if (selectedMediaType != null) {
//移除 quality 。例如,application/json;q=0.8 移除后为 application/json
selectedMediaType = selectedMediaType.removeQualityValue();
//遍历 messageConverters 数组
for (HttpMessageConverter<?> converter : this.messageConverters) {
//判断 HttpMessageConverter 是否支持转换目标类型
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
//拿到我们要响应的内容
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
//body 非空,则进行写入
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
//加头部
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
//调用消息转换器的write方法,将数据写出去
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
// return 返回,结束整个逻辑
return;
}
}
}
这里有一点需要注意:使用@ResponseBody注解的类,不会经过视图解析,因为
//在使用@ResponseBody,mv=null
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
至此,一次@ResponseBody的解析就完成了,但我们也知道,springMVC也有视图解析的方式,直接给用户返回一个渲染后的页面,只不过在前后分离的趋势下,越来越少用
- 返回页面的逻辑
套路和上面是一样的,通常情况,我们返回的其实是view名,只不过返回的负责处理的returnValueHandlers是ViewNameMethodReturnValueHandler
,supportsReturnType方法:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
// 判断的依据: 是否以redirect:开头
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
}
可见这里并没有进行实际的处理,只是解析得到了最终的视图名称。
- 目标方法处理的过程中,所有数据都会被放在
ModelAndViewContainer
里面。包括数据和视图地址
- 方法的参数是一个
自定义类型对象
(从请求参数中确定的),把他重新放在 ModelAndViewContainer
- 任何目标方法执行完成以后都会返回
ModelAndView(数据和视图地址)
。
bash这次返回的mv就不再是null了,
就可以进行视图解析了
- applyPostHandle:
执行拦截器post方法
//意思是:如果我们没有设置viewName,就采用默认的。否则采用我们自己的
applyDefaultViewName(processedRequest, mv);
// 步骤5,拦截器post方法,重要
// 执行所有的拦截器的postHandle方法,并且把mv给他
// 这里有一个小细节:这个时候拦截器是【倒序】执行的
mappedHandler.applyPostHandle(processedRequest, response, mv);
- 视图渲染:
processDispatchResult
,进行处理结果,渲染视图、处理异常
等等
- 由DispatcherServlet的
processDispatchResult
方法完成,源码:
//exception 执行处理器方法报错时被捕获的异常
//如果是@ResponseBody,那么这个方法里面的逻辑除了拦截器的逻辑,其他都不会执行
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//如果有异常,就进入异常处理逻辑,返回到异常页面
if (exception != null) {
// 含有异常页面视图的异常
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
//1、会执行所有的我们的自己配置(或者默认配置)了的HandlerExceptionResolver处理器
//2、上面需要注意了,但凡处理方法返回的不是null,有mv的返回。那后面的处理器就不会再进行处理了。具有短路的效果,一定要注意 是通过null来判断的
//3、处理完成后,得到error的视图mv,最后会设置一个viewName,然后返回出去
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
// 如果mv不为空,且没有被清理,也就是请求在此之前还没有响应浏览器,就开始执行render()方法,开始渲染视图了
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.");
}
}
//处理异步=========我们发现,它不执行后面的AfterCompletion方法了,注意一下即可
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
// 执行拦截器的AfterCompletion 方法
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
可以看出,处理根据是否抛出异常分为了两种情况
。
如果抛出了异常,那么processHandlerException方法将会遍历所有的HandlerExceptionResolver实例
,默认有哪些参考MVC初始化-HandlerExceptionResolver检查一节。默认的处理器用于改变响应状态码、调用标注了@ExceptionHandler的bean进行处理,如果没有@ExceptionHandler的bean或是不能处理此类异常,那么就会导致ModelAndView始终为null,最终Spring MVC将异常向上抛给Tomcat,然后Tomcat就会把堆栈打印出来。
如果我们想将其定向到指定的错误页面,可以这样配置:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="error"></property>
</bean>
- DispatcherServlet.render()
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
// 通过localeResolver吧local解析出来,放到response里面去
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
//==================视图:关键中的关键==================
View view;
//获取视图名称
String viewName = mv.getViewName();
// 如果已经有viewName了(绝大多数情况)
if (viewName != null) {
// We need to resolve the view name.
// 视图解析器 根据String类型的名字,解析出来一个视图(视图解析器有多个)
// 还是那个原理:只要有一个返回了不为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 {
//没有视图名称,但是必须有视图内容,否则抛出异常
// No need to lookup: the ModelAndView object contains the actual View object.
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() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
//设置响应码 status
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 根据model里的数据,正式渲染(关于此部分逻辑,后续再说,也比较复杂)
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
- DispatcherServlet.resolveViewName()
//解析viewName返回一个View对象
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
//判断当前的视图解析器集合是否为空
if (this.viewResolvers != null) {
//循环调用我们的视图解析器对象解析视图
for (ViewResolver viewResolver : this.viewResolvers) {
// 一旦有我们的视图解析器能够解析出视图,后面的视图解析器不在参与解析,直接返回
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
resolveViewName方法将会遍历所有的ViewResolver bean,只要有一个解析的结果(View)不为空,即停止遍历。
根据MVC初始化-ViewResolver检查一节和我们的配置文件可知,容器中有两个ViewResolver ,分别是: InternalResourceViewResolver和UrlBasedViewResolver
。resolveViewName其实只做了一件事: 用反射创建并初始化我们指定的View,根据我们的配置,就是JstlView
。
- processDispatchResult :处理派发结果(小结,
以redirect为例
)
- render(mv, request, response); 进行页面渲染逻辑
- 根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
■ 1、所有的视图解析器尝试是否能根据当前返回值得到View对象
■ 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
■ 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器
,内部还是利用下面所有视图解析器得到视图对象。
■ 4、view.render(mv.getModelInternal(), request, response);视图对象调用自定义的render进行页面渲染工作
4.1 RedirectView如何渲染【重定向到一个页面】
4.2 获取目标URL地址
4.3 response.sendRedirect(encodeURL)
- 视图解析小结:
- 返回值
有异常
: 遍历所有的HandlerExceptionResolver实例进行处理,或者向上抛给Tomcat- 返回值
以 forward
: 开始: new InternalResourceView(forwardUrl); -----> 转发request.getRequestDispatcher(path).forward(request, response);- 返回值
以 redirect
: 开始: new RedirectView() —> render就是重定向- 返回值是
普通字符串
:new ThymeleafView()[假设是使用Thymeleaf模板引擎]—>
此处理器会返回一个非空的ModelAndView。然后使用writer将数据写入到前端
我们可以自定义视图解析器+自定义视图
- ModelAndView
回过头来看一下这到底是个什么东西。类图: ModelAndView类图
很直白。
怎么生成的。RequestMappingHandlerAdapter.getModelAndView相关源码:
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
- ViewResolver
resolveViewName方法的源码不再贴出,其实只做了一件事: 用反射创建并初始化我们指定的View,根据我们的配置,就是JstlView
。
渲染的核心逻辑位于InternalResourceView.renderMergedOutputModel,简略版源码:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
// 将Model中的属性设置的request中
exposeModelAsRequestAttributes(model, request);
// 获取资源(jsp)路径
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
rd.include(request, response);
} else {
// Note: The forwarded resource is supposed to determine the content type itself.
rd.forward(request, response);
}
}
可以看出,对jsp来说,所谓的渲染其实就是将Model中的属性设置到Request,再利用原生Servlet RequestDispatcher API进行转发的过程。
- Model、View、ModelAndView对比
-
View为服务器上的某个文件容器`,可以为JSP,FTL等动态页面文件,甚至是媒体文件等等,单单是一个文件。
-
Model的作用是存储动态页面属性,动态页面文件即View可以在Model中获取动态数据,
Model其实是一个ModelMap类型,它是一个LinkedHashMap的子类
,作用类似于request对象的setAttribute方法的作用
(数据在一次请求转发中有效),用来在一个请求过程中传递处理的数据。 -
ModelAndView,顾名思义,
就是整合了Model和View,常用于动态页面
,一个后台网页就包含这两部分,前台就是基本的html代码
- 总结图
其实,在如今的前后端分离的浪潮下,试图解析这一流程也是越来越少,大部分都是返回数据供前端渲染