接口
public interface ViewResolver { /** * 通过viewName和locale查找View * @param viewName * @param locale * @return * @throws Exception */ @Nullable View resolveViewName(String viewName, Locale locale) throws Exception; }
类图
spring boot 默认用了以上几种
ContentNegotiatingViewResolver 为排列的第一个 他内部什么都不做只是维护了多个Resolver加了排序 所以我们通过这个为入口开始看源码
ContentNegotiatingViewResolver
initServletContext
org.springframework.web.servlet.view.ContentNegotiatingViewResolver#initServletContext
protected void initServletContext(ServletContext servletContext) { /** * 从容器中获得所有ViewResolver 我们可以自定义我们的ViewResolver */ Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values(); ViewResolver viewResolver; if (this.viewResolvers == null) { this.viewResolvers = new ArrayList(matchingBeans.size()); Iterator var3 = matchingBeans.iterator(); while(var3.hasNext()) { viewResolver = (ViewResolver)var3.next(); //排除当前对象 因为当前resolver也在容乃公器里面 if (this != viewResolver) { this.viewResolvers.add(viewResolver); } } } else { for(int i = 0; i < this.viewResolvers.size(); ++i) { viewResolver = (ViewResolver)this.viewResolvers.get(i); if (!matchingBeans.contains(viewResolver)) { String name = viewResolver.getClass().getName() + i; this.obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolver, name); } } } //根据@Order注解进行优先级排序 AnnotationAwareOrderComparator.sort(this.viewResolvers); this.cnmFactoryBean.setServletContext(servletContext); }
DispatcherServlet
org.springframework.web.servlet.DispatcherServlet#doDispatch
->
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
->
org.springframework.web.servlet.DispatcherServlet#render
render
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { //调用localeResolver Locale locale = this.localeResolver.resolveLocale(request); response.setLocale(locale); View view; if (mv.isReference()) { //<1>根据viewResolver获得view view = this.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 '" + this.getServletName() + "'"); } } else { //如果我们ModelAndView直接返回的View则直接获取Vew view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + this.getServletName() + "'"); } } if (this.logger.isDebugEnabled()) { this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'"); } try { //解析 view.render(mv.getModelInternal(), request, response); } catch (Exception var7) { if (this.logger.isDebugEnabled()) { this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var7); } throw var7; } }
<1>resolveViewName
org.springframework.web.servlet.DispatcherServlet#doDispatch
->
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
->
org.springframework.web.servlet.DispatcherServlet#render
->
org.springframework.web.servlet.DispatcherServlet#resolveViewName
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { //viewResolvers dispatcher init初始化 第一个为ContentNegotiatingViewResolver Iterator var5 = this.viewResolvers.iterator(); View view; do { if (!var5.hasNext()) { return null; } ViewResolver viewResolver = (ViewResolver)var5.next(); //调用<2>viewResolver 传入viewName和locale 获得View view = viewResolver.resolveViewName(viewName, locale); } while(view == null); return view; }
ContentNegotiatingViewResolver
org.springframework.web.servlet.DispatcherServlet#doDispatch
->
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
->
org.springframework.web.servlet.DispatcherServlet#render
->
org.springframework.web.servlet.DispatcherServlet#resolveViewName
->
org.springframework.web.servlet.view.ContentNegotiatingViewResolver#resolveViewName
<2>resolveViewName
public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); /** * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,q=0.8,application/signed-exchange;v=b3 * 获得request head里面的Accept值 ,号分割 * 此为http协议里面代表客户端期望接收的数据类型 */ List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest()); if (requestedMediaTypes != null) { /** * <3>遍历所有ViewResolve 获得view 注意:这里可能获取到多个 */ List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes); /** *<5>根据requestedMediaTypes 和attrs决策出一个最优的view */ View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs); if (bestView != null) { return bestView; } } String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : ""; if (this.useNotAcceptableStatusCode) { if (this.logger.isDebugEnabled()) { this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo); } //返回406的view /** * private static final View NOT_ACCEPTABLE_VIEW = new View() { * @Nullable * public String getContentType() { * return null; * } * * public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) { * response.setStatus(406); * } * }; */ return NOT_ACCEPTABLE_VIEW; } else { this.logger.debug("View remains unresolved" + mediaTypeInfo); return null; } }
<3>getCandidateViews
org.springframework.web.servlet.DispatcherServlet#doDispatch
->
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
->
org.springframework.web.servlet.DispatcherServlet#render
->
org.springframework.web.servlet.DispatcherServlet#resolveViewName
->
org.springframework.web.servlet.view.ContentNegotiatingViewResolver#resolveViewName
->
org.springframework.web.servlet.view.ContentNegotiatingViewResolver#getCandidateViews
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"); Iterator var5 = this.viewResolvers.iterator(); while(var5.hasNext()) { ViewResolver viewResolver = (ViewResolver)var5.next(); //遍历resolver选择合适的view View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { candidateViews.add(view); } Iterator var8 = requestedMediaTypes.iterator(); while(var8.hasNext()) { MediaType requestedMediaType = (MediaType)var8.next(); /** * <4>这里主要根据客户端期待的数据类型 获得后缀 再次调用resolve生成view默认都是获取空 * 比如我们viewname是index inde.jsp index.html index.xml * private final Set<MediaTypeFileExtensionResolver> resolvers; 默认为空 我们有需求可以给成员变量注入映射关系 */ List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType); Iterator var11 = extensions.iterator(); while(var11.hasNext()) { String extension = (String)var11.next(); //拼接后缀生成view String viewNameWithExtension = viewName + '.' + extension; view = viewResolver.resolveViewName(viewNameWithExtension, locale); if (view != null) { candidateViews.add(view); } } } } } //是否有配置默认view 如果有配置将默认views加入进去 。比如404 html的view ajax的view if (!CollectionUtils.isEmpty(this.defaultViews)) { candidateViews.addAll(this.defaultViews); } return candidateViews; } }
<4>resolveFileExtensions
org.springframework.web.servlet.DispatcherServlet#doDispatch
->
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
->
org.springframework.web.servlet.DispatcherServlet#render
->
org.springframework.web.servlet.DispatcherServlet#resolveViewName
->
org.springframework.web.servlet.view.ContentNegotiatingViewResolver#resolveViewName
->
org.springframework.web.servlet.view.ContentNegotiatingViewResolver#getCandidateViews
->
org.springframework.web.accept.ContentNegotiationManager#resolveFileExtensions
public List<String> resolveFileExtensions(MediaType mediaType) { Set<String> result = new LinkedHashSet(); //默认为空 获得resolvers Iterator var3 = this.resolvers.iterator(); while(var3.hasNext()) { MediaTypeFileExtensionResolver resolver = (MediaTypeFileExtensionResolver)var3.next(); //映射关系转换 result.addAll(resolver.resolveFileExtensions(mediaType)); } return new ArrayList(result); }
<5>candidateViews
@Nullable private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes, RequestAttributes attrs) { Iterator var4 = candidateViews.iterator(); while(var4.hasNext()) { //如果是重定向view 直接返回 View candidateView = (View)var4.next(); if (candidateView instanceof SmartView) { SmartView smartView = (SmartView)candidateView; if (smartView.isRedirectView()) { return candidateView; } } } var4 = requestedMediaTypes.iterator(); while(var4.hasNext()) { MediaType mediaType = (MediaType)var4.next(); Iterator var10 = candidateViews.iterator(); while(var10.hasNext()) { View candidateView = (View)var10.next(); if (StringUtils.hasText(candidateView.getContentType())) { //获得view 的contentType 并做叛逆的是否符合 MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType()); if (mediaType.isCompatibleWith(candidateContentType)) { if (this.logger.isDebugEnabled()) { this.logger.debug("Selected '" + mediaType + "' given " + requestedMediaTypes); } attrs.setAttribute(View.SELECTED_CONTENT_TYPE, mediaType, 0); return candidateView; } } } } return null; }
总结
1.ContentNegotiatingViewResolver并没有做View的查找工作 只是内部继承了其他Resolver并支持Order排序
2.同时提供支持通过MetaInfo从多个适配View适配最合适的view 因为dispacher适配一个就立即返回
3.我们可以直接返回view对象 如果返回一个字符串将通过ViewResolver做适配查找对应的View