上一篇文章介紹了SpringMvc的ControllerAdvice和ExceptionHandler,這裏在介紹一下ViewResolver的使用,並介紹一下HandlerMethodReturnValueHandler和ViewResolver的關係。html
自定義ResponseBody這篇文章介紹過ResponseBody的編碼規則,ViewResolver和ResponseBody是明顯互斥的。 這兩個不一樣類型的返回值就是經過不一樣的HandlerMethodReturnValueHandler來是實現的。 先看RequestResponseBodyMethodProcessor,這裏設置了setRequestHandled爲true,而後經過HttpMessageConverters編碼對應的model。java
public void handleReturnValue(Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); // Try even with null return value. ResponseBodyAdvice could get involved. writeWithMessageConverters(returnValue, returnType, webRequest); }
再看ViewNameMethodReturnValueHandler,這裏沒有設置setRequestHandled,而是取出CharSequence類型的返回值,並賦值viewName。git
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue instanceof CharSequence) { String viewName = returnValue.toString(); mavContainer.setViewName(viewName); if (isRedirectViewName(viewName)) { mavContainer.setRedirectModelScenario(true); } } else if (returnValue != null){ // should not happen throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } }
再看RequestMappingHandlerAdapter中使用方法,假如mavContainer.isRequestHandled是true直接返回null,後面就不會調用ViewResolver了。假如是false,會取出mavContainer.getViewName,返回ModelAndView,後面會根據ViewName進行模板的映射。github
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; } ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; }
相比ExceptionHandler,自定義ViewResolver就比較簡單了,只要注入一個ViewResolver的實現類就能夠了。 不過爲了介紹ViewResolver的原理,這裏自定義了一個HandlerMethodReturnValueHandler來取代ViewNameMethodReturnValueHandler; 完整的代碼仍是在Github上了。web
@Controller public static class ControllerClass { @RequestMapping public ViewName index(ModelMap modelMap) { modelMap.put("message", "hello world"); ViewName viewName = new ViewName(); viewName.setName("index"); return viewName; } @RequestMapping("html") public ViewName htmlIndex(ModelMap modelMap) { modelMap.put("message", "hello world"); ViewName viewName = new ViewName(); viewName.setName("html"); return viewName; } } public static class ViewName { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
@Configuration public static class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport { public void configureViewResolvers(ViewResolverRegistry registry) { MyViewResolver myViewResolver = new MyViewResolver(); registry.viewResolver(myViewResolver); } public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) { returnValueHandlers.add(new MyHandlerMethodReturnValueHandler()); } }
public static class MyHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler { public boolean supportsReturnType(MethodParameter returnType) { return returnType.getParameterType().isAssignableFrom(ViewName.class); } public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { ViewName viewName = (ViewName) returnValue; mavContainer.setViewName(viewName.getName()); } }
public static class MyViewResolver implements ViewResolver { private View htmlView = new MyHtmlView(); private View view = new MyView(); public View resolveViewName(String viewName, Locale locale) throws Exception { if (viewName.equals("index")) { return view; } else if (viewName.equals("html")) { return htmlView; } return null; } } public static class MyView implements View { public String getContentType() { return "text/html"; } public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { StringBuilder stringBuilder = new StringBuilder(); for (Map.Entry<String, ?> entry : model.entrySet()) { stringBuilder.append(entry.getKey()).append(":").append(entry.getValue()); } response.getWriter().write(stringBuilder.toString()); } } public static class MyHtmlView implements View { public String getContentType() { return "text/html"; } public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { String head = "<html><head><title>Hello World</title></head><body><ul>"; String tail = "</ul></body></html>"; StringBuilder sb = new StringBuilder(); sb.append(head); for (Map.Entry<String, ?> entry : model.entrySet()) { sb.append("<li>").append(entry.getKey()).append(":").append(entry.getValue()).append("</li>"); } sb.append(tail); response.getWriter().write(sb.toString()); } }
@Configuration public class CustomizeViewResolverTest { public static void main(String[] args) throws ServletException, IOException { MockServletContext mockServletContext = new MockServletContext(); MockServletConfig mockServletConfig = new MockServletConfig(mockServletContext); AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext(); annotationConfigWebApplicationContext.setServletConfig(mockServletConfig); annotationConfigWebApplicationContext.register(CustomizeViewResolverTest.class); DispatcherServlet dispatcherServlet = new DispatcherServlet(annotationConfigWebApplicationContext); dispatcherServlet.init(mockServletConfig); MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); dispatcherServlet.service(request, response); System.out.println(new String(response.getContentAsByteArray())); MockHttpServletResponse htmlResponse = new MockHttpServletResponse(); MockHttpServletRequest htmlRequest = new MockHttpServletRequest("GET", "/html"); dispatcherServlet.service(htmlRequest, htmlResponse); System.out.println(new String(htmlResponse.getContentAsByteArray())); } }
運行程序就會發現,一個輸出了文章,另外一個輸出了html格式的文字。spring
ViewResolver和ResponseBody都是用來處理HttpResponseBody的內容的,只不過處理的方式不一樣。 ResponseBody的編碼方式是同樣的,通常是處理JSON的編碼。ViewResolver還能夠一根據ViewName來路由到不用的View,每一個View均可以本身的編碼規則。app