1、什么是视图解析器?视图解析器的作用是啥?视图解析器在整个请求的的过程中执行时机在哪里?
答:视图解析器就是SpringMVC中定义视图的信息解析的组件。
视图解析器的作用就是根据视图名称viewName + 本地化信息Locale来解析出一个View视图。
视图解析器的执行时机在,HandlerMappingAdapter执行结束后会得到一个ModelAndView类型的实例,然后视图解析器会根据这个ModelAndView中封装的信息来解析出一个View实例。
2、ViewResolver接口定义以及类图:
public interface ViewResolver {
/**
* Resolve the given view by name.
* <p>Note: To allow for ViewResolver chaining, a ViewResolver should
* return {@code null} if a view with the given name is not defined in it.
* However, this is not required: Some ViewResolvers will always attempt
* to build View objects with the given name, unable to return {@code null}
* (rather throwing an exception when View creation failed).
* @param viewName name of the view to resolve
* @param locale Locale in which to resolve the view.
* ViewResolvers that support internationalization should respect this.
* @return the View object, or {@code null} if not found
* (optional, to allow for ViewResolver chaining)
* @throws Exception if the view cannot be resolved
* (typically in case of problems creating an actual View object)
*/
根据view的名称 + 本次请求的本地化信息实例 来解析出一个View实例。
View resolveViewName(String viewName, Locale locale) throws Exception;
}
类图的结构比较庞大,我们挑重要的进行展示即可:
3、常用的视图解析器InternalResourceViewResolver配置方式:
InternalResourceViewResolver这个是我们常用的视图解析器,其配置方式如下:
<!-- 配置一个试图解析器,浅前缀是我们存放jsp的目录,后缀是指定类型是.jsp -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver" >
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
这样配置就是在解析视图的时候,会使用prefix + ViewName + suffix 来定位到jsp文件,然后将jsp文件进行解析,然后生成一个View实例,View也是一个接口,不同实现的ViewResolver解析的View类型也是不一样的,View的实现也是也别丰富,比如有JstlView(可进行jstl标签转换的视图)、FreeMarkView(基于FreeMark进行模板替换的视图)、VelocityView等实现。
4、ViewResolver在整个请求的执行时机解析:
案例:这个案例跟之前一篇讲解返回值处理器的时候是一样的,注意@ResponseBody这种返回值方式是不会有视图解析的,在讲解具体的返回值处理器实现类RequestResponseBodyMethodProcessor的时候有明确的实现解析。
@RequestMapping("test/save")
public String test(@Validated(value = HumaSaveGroup.class) Huma huma, BindingResult bindingResult) {
huma.setAge(18);
return "register"; //这个register是一个viewName,比如jsp的名称
}
案例中返回的“register”就是我们定义的一个jsp文件的名称,它的路径如下图:
开始解析ViewResolver执行点:在之前解析返回值处理器的时候我们讲到了RequestMappingHnadlerAdapter的invokeHandlerMethod(...)方法,我们今天依旧从这里开始,核心代码:
获取一个ModelAndView实例并返回,这里就是整个RequestMappingHnadlerAdapter的结果返回实现。
return getModelAndView(mavContainer, modelFactory, webRequest);
获取ModelAndView实例的实现如下:
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
1、根据ModelAndViewContainer实例里的model数据取更新请求里的属性数据,也就是将需要存爱Sesssion里面的model进行存储等操作。
modelFactory.updateModel(webRequest, mavContainer);
2、如果本次请求是已经处理结束了的,就返回null,之前说的@ResponseBody的这种方式在此处就不会返回一个ModelAndView实例,而是直接返回null.
if (mavContainer.isRequestHandled()) {
return null;
}
3、如果请求并没有处理结束,就获取ModelAndViewContainer 实例里的model数据。
ModelMap model = mavContainer.getModel();
4、viewName是在返回值处理器里面设置的,在ModelAndViewContainer中view是一个Object类型在
返回值处理器中设置的时候其实view属性就是一个String类型的viewName,使用其 + model数据构建一个
ModelAndView实例。
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
5、如果ModelAndViewContainer实例中的view属性不是String类型的,那就设置ModelAndView实例的view是ModelAndViewContainer实例的view实例。
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
6、如果model实例的类型是RedirectAttributes重定向相关的类型就进行一些处理,此处不是核心暂且跳过。
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
7、最终返回一个ModelAndView实例。
return mav;
}
有了HandlerMappingHandlerAdapter返回的ModelAndView实例后,我们来到DispatcherServlet的doDispatch(...)方法中继续整个请求的流程:
...
此处mv就是返回的ModelAndView实例。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
处理器默认的viewName,就是判断ModelAndView实例中是否有view属性,如果没有的话就设置为配置的默认发ViewName
applyDefaultViewName(processedRequest, mv);
执行mvc拦截器的后置处理方法。
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);
我们来到:processDispatchResult(...)方法:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
1、如果在HandelrMethod查找中+执行处理器过程中产生异常的话就进行异常处理,这个在下一篇文章给你剖析全局异常处理器的时候会进行详细介绍。
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);
}
}
2、如果执行正常且ModelAndView实例不为空 且 ModelAndView是没有被清除的默认就是不被清除的。
// Did the handler return a view to 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);
}
}
呈献视图:render(...)实现:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
1、先使用本地化信息解析器解析到当前的本地化信息实例Locale。
Locale locale = this.localeResolver.resolveLocale(request);
2、设置响应实例的本地化信息。
response.setLocale(locale);
3、使用视图解析器解析出一个View实例,这里就是视图解析器的工作点。
View view;
if (mv.isReference()) {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), 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());
}
4、解析成视图后进行视图呈献,就是输出到响应中,然后借结束请求。
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;
}
}
5、解析了ViewResolver的执行点后,我们知道了整体的工作流程,至于后面的解析视图、视图呈献的原理这里就不展开了。