springmvc–ViewResolver
和View
1 前言
虽然现在前后端分离是主流,但是我们还是需要了解一下传统项目。当我们在处理器方法中返回一个String
字符串并且没有使用@ResponseBody
注解的时候,springmvc
就会使用视图解析器解析这个String
字符串得到视图对象,最终调用视图对象的render()
方法完成视图渲染,响应给客户端。
2 视图解析和响应原理
经过前面的学习,我们知道DispatcherServlet
在它的doDispatch(request, response)
方法中完成请求的处理和响应,下面是该方法的源码
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
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;
}
}
//执行拦截器applyPreHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//处理器适配器调用处理器方法, 并返回一个ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//没有指定视图名,则使用默认的视图名
applyDefaultViewName(processedRequest, mv);
//执行拦截器applyPostHandle()方法
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);
}
//处理异常和ModelAndView对象,使用视图解析器解析视图,响应视图
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);
}
}
}
}
我们重点看一下
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
方法,该方法调用视图解析器解析视图并响应给客户端
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
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 {
//解析异常,生成一个ModelAndView对象
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//解析ModelAndView对象,生成视图对象,渲染视图,响应给客户端浏览器
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);
}
}
重点看这个
render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
方法
/**
* Render the given ModelAndView.
* <p>This is the last stage in handling a request. It may involve resolving the view by name.
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws ServletException if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
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.
//解析视图名得到视图对象,方法内部遍历它拥有的所有的视图解析器,获取第一个解析成功的视图对象
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;
}
}
其实视图解析原理很简单:
处理器方法返回一个
String
字符串,这个字符串被视图解析器解析,生成视图对象,最后调用视图对象的render()
方法渲染视图,响应给浏览器。
3 视图解析器
先来看一下springmvc
定义的视图解析器接口
public interface ViewResolver {
/**
* 解析视图名,得到视图对象
*/
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
这个接口只有一个方法,就是根据视图名得到视图对象。我们再看一下
springmvc
提供的默认的视图解析器
上面这些就是springmvc
提供的默认的视图解析器
视图解析器 | 作用 |
---|---|
UrlBasedViewResolver | 解析得到转发和重定向视图 |
InternalResourceViewResolver | 解析得到jsp 视图 |
ContentNegotiatingViewResolver | 根据客户端可接收的内容类型调用其他的视图解析器创建视图对象 |
FreeMarkerViewResolver | 解析得到FreeMaker 模板视图 |
GroovyMarkupViewResolver | 解析得到Groovy XML/XHTML 模板视图 |
BeanNameViewResolver | 根据视图名从spring 容器中获取对应的视图对象 |
ResourceBundleViewResolver | 和BeanNameViewResolver 很相似,只是该视图解析器只会默认获取views包 定义的视图bean 对象 |
XmlViewResolver | 和BeanNameViewResolver 很相似,只是该视图解析器只会默认获取/WEB-INF/views.xml文件 定义的视图bean 对象 |
视图解析器只负责将视图名解析为对应的视图对象,视图渲染过程由视图对象的
render()
方法完成
4 视图
先来看一下springmvc
定义的视图接口
public interface View {
/**
* 响应状态码
* org.springframework.web.servlet.View.responseStatus
*/
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
/**
* 模板变量
* org.springframework.web.servlet.View.pathVariables
*/
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
/**
* 选中的视图类型
* org.springframework.web.servlet.View.selectedContentType
*/
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
//获取响应的内容类型
@Nullable
default String getContentType() {
return null;
}
//渲染视图
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
这个接口的核心方法就是
render()
方法,它负责将视图内容写入到响应体中。另外还定义了3
个常量
RESPONSE_STATUS_ATTRIBUTE
PATH_VARIABLES
SELECTED_CONTENT_TYPE
上面这些就是springmvc
提供的默认的视图
视图解析器 | 作用 |
---|---|
InternalResourceView | jsp 视图 |
JstlView | 可解析jstl 标签的jsp 视图 |
RedirectView | 重定向视图 |
MappingJackson2JsonView | 响应json 数据的视图 |
MappingJackson2XmlView | 响应xml 数据的视图 |
FreeMarkerView | 可解析FreeMarkerView 标签的视图 |
GroovyMarkupView | 可解析Groovy 标签的视图 |
AbstractXlsxView | excel 表格下载的视图,需要用户自己实现具体逻辑 |
springmvc
提供很多默认的视图类型,用户可以根据需要选择合适的视图响应