一、前言
web相关知识系列从1-7,完成了从一个请求从浏览器发出,是如何适配后端接口,请求参数是如何与接口参数进行绑定,再到接口处理完数据后,返回值是如何返回给浏览器以及如何与浏览器接受类型进行适配的,从而形成了一个闭环,web相关知识探索七中主要是研究接口方法使用了注解@ResponseBody返回值是如何转为浏览器能够接收的对应数据类型的,本章主要研究的是视图解析原理流程。
二、视图解析原理
一、测试用例
类上面使用的是@Controller注解,而不是@RestController
@PostMapping("/test/login")
public String login(@RequestBody Person person, HttpSession session,Model model){
if (Objects.nonNull(person) && StrUtil.isNotBlank(person.getUserName())
&& "123456".equalsIgnoreCase(person.getPwd())){
session.setAttribute("loginUser",person);
return "redirect:/index.html";
}else{
model.addAttribute("msg","账号密码不匹配");
return "login";
}
}
二、原理
web相关知识系列7中由于使用了@ResponseBody,被底层源码中的
ServletInvocableHandlerMethod这个对象的invokeAndHandle这个方法中的直接将数据写出
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { // 这里选择使用哪个处理器处理接口返回来的数据使用了@ResponseBody会匹配到 //RequestResponseBodyMethodProcessor这个对象 HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } else { handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } }RequestResponseBodyMethodProcessor对象中的handleReturnValue
方法匹配上,直接将数据转为浏览器能够接受的类型,然后通过方法
@Override 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); }将数据直接写出去。
一、公共逻辑
在HandlerMethodReturnValueHandlerComposite类中的handleReturnValue方法开始出现差别。
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 这里开始选择处理返回值的handler,之前是使用@ResponseBody注解,所以这里会选择到 RequestResponseBodyMethodProcessor这个处理类进行处理
HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
} else {
// 这里就是调用通过判定的类对接口返回值的处理方法
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
}
二、使用了 @ResponseBody注解的处理逻辑
@ResponseBody这个注解,对应RequestResponseBodyMethodProcessor这个处理类型中的
public boolean supportsReturnType(MethodParameter returnType) { return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class); }这个方法,进行判断是否支持解析返回值。而这个方法的判断条件就是有没有使用ResponseBody这个注解。
通过了这个类的判定方法,接下来就会调用这个类对接口返回数据的处理。也就回到之前研究的逻辑了。具体不详细叙述。
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// 使用了视图容器的只有这行代码,也就是之后都没有用到视图容器
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
// 在这里就将数据写出到返回域中
this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
三、未使用@ResponseBody的处理逻辑
未使用 @ResponseBody这个注解,对应ViewNameMethodReturnValueHandler这个类的处理逻辑。首先会调用他的判定方法,判断是否支持当前接口返回数据的处理。
public boolean supportsReturnType(MethodParameter returnType) { Class<?> paramType = returnType.getParameterType(); return Void.TYPE == paramType || CharSequence.class.isAssignableFrom(paramType); }这里的判断逻辑就是返回值是Void类型的,以及返回值是字符串类型的都能够支持。之后会调用他对返回数据的处理方法。也就是handleReturnValue这个方法。
下面是处理返回值方法handleReturnValue的具体逻辑。
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 如果返回值是字符串将进入下面逻辑
if (returnValue instanceof CharSequence) {
// 将返回值变为字符串
String viewName = returnValue.toString();
// 放到 ModelAndViewContainer 容器中
mavContainer.setViewName(viewName);
// 这里会继续判断是不是重定向,判断逻辑就是 PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:") 这个。这就是为什么我们如果要重定向的话需要再返回值处写死redirect:这个字符串的原因。只有识别到了这个字符串才会进行处理。
if (this.isRedirectViewName(viewName)) {
// 如果是重定向,设置标识为true
mavContainer.setRedirectModelScenario(true);
}
} else if (returnValue != null) {
throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
当公共逻辑方法
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);里面的逻辑执行完,会进入到下面的处理流程中。RequestMappingHandlerAdapter类里面的invokeHandlerMethod方法
RequestMappingHandlerAdapter类中的getModelAndView方法
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
// 更新model,mavContainer对想里面放入了Person对象。方法的参数是一个自定义类型对象(从请求参数中确定的),会重新放在 ModelAndViewContainer 中华
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
// 这个model就是接口参数中的model对象
ModelMap model = mavContainer.getModel();
// 将接口返回的数据,也就是viewName和model一起封装成一个ModelAndView对象
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
// 判断model是不是RedirectAttributes类型,也就是说接口参数中的model可以是RedirectAttributes类型的参数
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;
}
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);// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}// Actually invoke the handler. 这里就是实际执行的handler,也就是前面一直研究的内容,这里没有使用@ResponseBody注解后,当前测试用了返回了ModelAndView对象,包含了重定向地址
// 任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 这里如果防线mv对象不为空,但是没有view对象,就会设置一个默认的view对象
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 这里处理派发结果(页面该如何响应)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
下面是处理派发结果逻辑
// 处理派发接口,视图解析的真正逻辑就在这里
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);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render? 方法直接回进入到这里,上面是出现异常才会走的,ModelAndView不为空且没有被清理
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.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
// 将进行页面渲染逻辑
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response. 国际化处理
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.这里将去解析视图名,传入视图名,model对象以及请求参数得到视图对象View,这个对象定义了如何去渲染页面。
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 {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 经过下面步骤后,得到视图对象,将会调用视图接口中的render方法,决定如何响应数据出去
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
// 这里是具体如何通过视图名得到一个view对象的
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
/**遍历视图解析器,这里有五个视图解析器分别是 ContentNegotiatingViewResolver(这个解析器进去,也是循环遍历系统中所有的解析器进行解析,相当于这个解析器就包含了其他四个解析器,具体逻辑如下)
** BeanNameViewResolver ViewResolverComposite InternalResourceViewResolver ThymeleafViewResolver(如果没有添加Thymeleaf依赖就没有这个解析器)
**相当于说,这段循环代码在ContentNegotiatingViewResolver处就可以解析了,轮不到其他解析器
**/
if (this.viewResolvers != null) {
// 循环遍历系统中的解析器看看那个解析器可以解析出视图对象
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
// ContentNegotiatingViewResolver这个解析器就可以解析出view对象
@Override
@Nullable
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) {
// 获取处理后的视图集合,可能会出现多个解析器都能够通过这个视图名解析出这个视图对象
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
// 这里就是拿到最合适的那个视图对象,相当于结合浏览器进行内容协商
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
// ContentNegotiatingViewResolver类中的方法
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
// 再次遍历所有的视图解析器,就是这里ContentNegotiatingViewResolver解析器相当于包含了所有解析器的作用
for (ViewResolver viewResolver : this.viewResolvers) {
// 尝试看看那个视图解析器能够解析,每个解析器的逻辑是不同的,这里不深究。
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
for (MediaType requestedMediaType : requestedMediaTypes) {
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
for (String extension : extensions) {
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
上面拿到了视图对象之后,调用自定义的render进行页面渲染工作,具体如下
// 自定义视图渲染 AbstractView类中的方法
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("View " + formatViewName() +
", model " + (model != null ? model : Collections.emptyMap()) +
(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
// 这里就是获取接口传入进来的model对象
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
// 这里进行具体的渲染逻辑
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
// RedirectView类中的方法
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws IOException {
// 获取目标路径,拼接重定向需要携带的参数等等操作
String targetUrl = createTargetUrl(model, request);
targetUrl = updateTargetUrl(targetUrl, model, request, response);
// Save flash attributes
RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
// Redirect 然后进行重定向
sendRedirect(request, response, targetUrl, this.http10Compatible);
}
// RedirectView类中的方法
protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
String targetUrl, boolean http10Compatible) throws IOException {
// 重定向路径
String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
if (http10Compatible) {
HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
if (this.statusCode != null) {
response.setStatus(this.statusCode.value());
response.setHeader("Location", encodedURL);
}
else if (attributeStatusCode != null) {
response.setStatus(attributeStatusCode.value());
response.setHeader("Location", encodedURL);
}
else {
// Send status code 302 by default.这里就是调用了最原始的HttpServletResponse重定向方法,将会重定向到一个页面
response.sendRedirect(encodedURL);
}
}
else {
HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
response.setStatus(statusCode.value());
response.setHeader("Location", encodedURL);
}
}
总的来说:
1、返回值以 forward: 开头的,将会是在这个对象new InternalResourceView(forwardUrl)处理,这个对象中的处理方法调用的也是request.getRequestDispatcher(path).forward(request, response),最原始的servlet方法进行转发。
2、返回值以 redirect: 开头的,将会以new RedirectView()这个对象进行处理,调用里面的reder方法,里面也是使用原始的response.sendRedirect(encodedURL)方法进行转发
3、其他类似使用了Thymeleaf模板引擎的就不细究了
三、视图解析原理流程总结
一、视图解析原理流程
(1)、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
(2)、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
(3)、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
(4)、processDispatchResult 处理派发结果(页面改如何响应)
1、render(mv, request, response); 进行页面渲染逻辑
1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑
1、所有的视图解析器尝试是否能根据当前返回值得到View对象
2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
a、RedirectView 如何渲染【重定向到一个页面】
b、获取目标url地址
c、response.sendRedirect(encodedURL);