视图解析原理
以上述的redirect前缀的重定向视图为例,观察源码运行
首先debug,然后控制器方法打断点,开始快乐
一切请求先看DispatcherServlet
来到DispatcherServlet中
在doDispatch方法中
先是确定当前请求的处理方法
mappedHandler = this.getHandler(processedRequest);
然后接着往下看
再就是熟悉的获取请求适配器,从4个适配器里面找出来一个合适的,当前使用的注解是@RequestMapping,所以适配器是ha = {RequestMappingHandlerAdapter@6686}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
接着来到这一行,处理请求的这一行
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
进入,经过几次跳转会来到这里。熟悉的开始,进入
mav = invokeHandlerMethod(request, response, handlerMethod);
直到来到这,这里是处理请求和执行控制器方法的
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//第一行执行完会执行控制器方法
setResponseStatus(webRequest);
之后就拿到了控制器返回值,ReturnValue
再往下就是处理返回结果,这一句,进入
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
会看见
//这一行是找返回值解析器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
//这一行是处理返回值
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
在selectHandler中
循环所有返回值解析器,找到能解析的那一个
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
//找到了一个叫12 = {ViewNameMethodReturnValueHandler@7616} 的解析器,他的supportsReturnType
方法中有一条,只要返回值是字符串就能解析,所以解析器是它
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
然后在handleReturnValue中
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue instanceof CharSequence) {
//在这里把返回值设置为mav的视图名,返回值是:redirect:/main
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else if (returnValue != null) {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
然后会结束一些方法的执行,然后会获取mav的内容,进入
return getModelAndView(mavContainer, modelFactory, webRequest)
会来到
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
//获取model值,没有就创建一个空的
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
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;
}
返回值 ModelAndView [view="redirect:/main"; model={}]
然后就一路返回来到,这一句就执行完了,因为没有请求参数,处理的步骤不那么多。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
接下来,已经拿到了初步处理的mav,继续执行下去,会看见这一行,进入
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
会来到
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//有mv,从这一行进入
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
来到render方法中,在方法上他的注中也说到这是处理请求的最后一个步骤,可能会根据名称解析视图
在 render方法中
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
//拿一些 国际化的内容确定编码格式什么的,执行下去后就能看见utf-8、zh_cn等等
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
//解析视图名,然后进入
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
........
在上面解析视图名时,用一个view对象接收。在View对象中
在进入View对象后,发现他是一个接口,里面就一个方法,方法中提供了model、request、response参数用来渲染对象。这说明View对象是经过渲染后,再进行getDispather或redirect的
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
进入后
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
//内容协商
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
//获取第一次解析后的初始的候选视图名,也可以进去看一下,在里面就是for循环视图解析器,看那个能解析就解析出来,然后放进候选数组中
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
可以看见当前视图解析器有5个,而第一个内容协商视图解析器就能解析这个视图名,进入后
进入后发现在内容协商视图解析器中有其它4个解析器
再次进行for循环寻找解析视图名解析器,循环到【到每个视图解析器都应该进入看看是怎么匹配解析类型的】thymeleafViewResolver时,进入会发现在里面就创建了一个名字为RerurnValue的view对象,然后返回,在最后一个InternalResourceViewResolver也能解析这个返回值的视图名
这个createView方法中
protected View createView(final String viewName, final Locale locale) throws Exception {
.........
// Process redirects (HTTP redirects)
//通过判断viewName的开头是不是它指定的几种中的一种,是就返回一个View对象。当前的viewname为redirect:/main,符合条件
默认值
public static final String REDIRECT_URL_PREFIX = "redirect:";
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
vrlogger.trace("[THYMELEAF] View \"{}\" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName);
final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length(), viewName.length());
final RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, REDIRECT_URL_PREFIX);
//最后返回一个view对象
}
......
}
所以最后 返回了两个视图解析器,得到后就会去选择最佳匹配结果,进入best那一行
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
会来到getBestView方法中,里面还是一个循环,确定最佳结果,在循环第一个结果,就是 thymeleafViewResolver解析出来的时候,判断是不是转发视图 ,直接返回true,所以返回的是thymeleaf解析的结果。
for (View candidateView : candidateViews) {
if (candidateView instanceof SmartView) {
SmartView smartView = (SmartView) candidateView;
if (smartView.isRedirectView()) {
return candidateView;
}
}
}
继续往下执行会来到这一行,渲染视图
view.render(mv.getModelInternal(), request, response);
进入后,这一行实现视图重定向,进入
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
进入后
sendRedirect(request, response, targetUrl, this.http10Compatible);
再进入,先是判断有没有转发数据,然后直接重定向,简单吧
response.sendRedirect(encodedURL);
前缀为forword的,render的时候就用的是request.getDispatch方法