深入Spring:自定義ViewResolver

前言

上一篇文章介紹了SpringMvc的ControllerAdviceExceptionHandler,這里在介紹一下ViewResolver的使用,并介紹一下HandlerMethodReturnValueHandlerViewResolver的關系。

ViewResolver和HandlerMethodReturnValueHandler

自定義ResponseBody這篇文章介紹過ResponseBody的編碼規則,ViewResolverResponseBody是明顯互斥的。
這兩個不同類型的返回值就是通過不同的HandlerMethodReturnValueHandler來是實現的。
先看RequestResponseBodyMethodProcessor,這里設置了setRequestHandledtrue,然后通過HttpMessageConverters編碼對應的model。

    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。

    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進行模板的映射。

    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;
    }

自定義ViewResolver

相比ExceptionHandler,自定義ViewResolver就比較簡單了,只要注入一個ViewResolver的實現類就可以了。
不過為了介紹ViewResolver的原理,這里自定義了一個HandlerMethodReturnValueHandler來取代ViewNameMethodReturnValueHandler;
完整的代碼還是在Github上了。

  1. 定義Controller。ViewName自定義類來包裝viewName。
    @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;
        }
    }
  1. 注入自定義的ViewResolversHandlerMethodReturnValueHandler
    @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());
        }
    }
  1. 自定義HandlerMethodReturnValueHandler,指定只支持ViewName這個類,處理時,取出來ViewNamename,設置到ModelAndViewContainer中。
    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());
        }
    }
  1. 自定義ViewResolver,定義了MyViewMyHtmlView一個處理index返回純文字,另一個處理html返回html格式。
    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());
        }
    }
  1. 程序入口,這里同時請求了//html分別用到了MyViewMyHtmlView
    @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格式的文字。

結語

ViewResolverResponseBody都是用來處理HttpResponseBody的內容的,只不過處理的方式不同。
ResponseBody的編碼方式是一樣的,一般是處理JSON的編碼。ViewResolver還可以一根據ViewName來路由到不用的View,每個View都可以自己的編碼規則。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,767評論 18 399
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • 前言 上一篇文章介紹了SpringMvc的RequestMappingHandlerMapping,自定義了Con...
    wcong閱讀 14,861評論 0 9
  • 多態 任何域的訪問操作都將有編譯器解析,如果某個方法是靜態的,它的行為就不具有多態性 java默認對象的銷毀順序與...
    yueyue_projects閱讀 989評論 0 1
  • 累呀,很累,忙了一整天了。 寶寶睡了,我忍不住要為寶媽吶喊。 早起八點多,挺享受的吧...
    人生留白_81e6閱讀 420評論 4 5