概述
本章开始进入另一重要的组件,即视图组件,Spring MVC处理视图组件使用两个主要的接口是ViewResolver和View。根据名称可知,ViewResolver即视图解析器,其作用是把逻辑视图名称解析为真正的视图,然后通过View对象进行渲染。View接口的作用是用于处理视图进行渲染。
延用之前的介绍流程,本章分两部分进行阐述:启动初始化和请求处理。
本系列文章是基于Spring5.0.5RELEASE。
ViewResolver初始化
Spring MVC初始化视图解析器策略与初始化其他策略一样,其入口是DispatcherSerlvet的initStrategies(context)方法,代码如下:
/**
*初始化策略对象
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
// 初始化视图解析器ViewResolver
initViewResolvers(context);
initFlashMapManager(context);
}
看过之前文章的可以了解,此方法是Spring MVC初始化策略组件的入口。针对视图解析器组件,调用initViewResolver(context)方法,加载视图处理策略,该方法源码如下:
private void initViewResolvers(ApplicationContext context) {
// viewResolvers是视图解析器集合,接收到用户请求时,从该属性变量中获取到Spring MVC使用的视图解析器
this.viewResolvers = null;
// 是否从Spring上下文中加载ViewResolver,detectAllViewResolvers属性变量默认为true,可在web部署描述文件中修改
if (this.detectAllViewResolvers) {
// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
// 按接口类型ViewResolver查找全部
// key为bean的id(name),value为bean的class对象
Map<String, ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<>(matchingBeans.values());
// We keep ViewResolvers in sorted order.
// 对ViewResolver进行排序,通过Ordered接口实现
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
}
else {
try {
// 从Spring上下文中加载指定名字为"viewResolver"的bean,作为视图解析器
ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
this.viewResolvers = Collections.singletonList(vr);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default ViewResolver later.
}
}
// Ensure we have at least one ViewResolver, by registering
// a default ViewResolver if no other resolvers are found.
// 为了确保至少有一个ViewResolver视图解析器,Spring MVC配置了默认的ViewResolver
// 在DispatcherServlet.properties文件中定义,默认为InternalResourceViewResolver
if (this.viewResolvers == null) {
this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
}
}
}
至此,Spring MVC即完成加载初始化ViewResolver视图解析器,即Spring MVC以具备解析处理逻辑视图名称的能力。
ViewResolver使用
ViewResolver的使用是指用户发起请求到Spring,Spring MVC经过HandlerMapping查找处理的handler,然后通过HandlerAdapter进行适配后处理用户请求,返回ModelAndView,最后使用ViewResolver对ModelAndView进行解析,即把逻辑视图名解析为真正的视图对象,由视图对象进行渲染的过程。
用户的请求处理流程由DispatcherServlet的doDispatcher(request,response)方法进行控制,主要代码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
... ...
// 找到请求对应的handler
mappedHandler = getHandler(processedRequest);
... ...
// 找到对应handler的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
... ...
// 执行用户请求的拦截器前置处理方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用实际的handler处理方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
... ...
// 设置默认的逻辑视图名称
// 如果handler返回null或者ViewName属性为null时,Spring进行取视图规则为:前缀+url+后缀
// 比如访问url为:http://localhost:8086/test,前缀配置/WEB-INF/jsp,后缀配置为.jsp,那么最终查找的是:/WEB-INF/jsp/test.jsp
applyDefaultViewName(processedRequest, mv);
// 执行用户请求的拦截器后置处理方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
... ...
//
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
/*
*处理结果
*/
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?
// 调用render方法
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
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()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
/*
*渲染ModelAndView
*/
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对象
// 此处是视图解析器使用入口,见下面的方法
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.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 调用View的render方法进行视图渲染
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
@Nullable
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) {
// 调用ViewResovler接口入口方法
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
总结
本章概述了Spring MVC加载视图解析器策略,并且找到了ViewResolver的入口方法,下面的章节继续学习ViewResolver接口的实现类。
最后创建了qq群方便大家交流,可扫描加入,同时也可加我qq:276420284,共同学习、共同进步,谢谢!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。