深刻Spring:自定义ViewResolver

前言

上一篇文章介绍了SpringMvc的ControllerAdviceExceptionHandler,这里在介绍一下ViewResolver的使用,并介绍一下HandlerMethodReturnValueHandlerViewResolver的关系。html

ViewResolver和HandlerMethodReturnValueHandler

自定义ResponseBody这篇文章介绍过ResponseBody的编码规则,ViewResolverResponseBody是明显互斥的。 这两个不一样类型的返回值就是经过不一样的HandlerMethodReturnValueHandler来是实现的。 先看RequestResponseBodyMethodProcessor,这里设置了setRequestHandledtrue,而后经过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;
	}

自定义ViewResolver

相比ExceptionHandler,自定义ViewResolver就比较简单了,只要注入一个ViewResolver的实现类就能够了。 不过为了介绍ViewResolver的原理,这里自定义了一个HandlerMethodReturnValueHandler来取代ViewNameMethodReturnValueHandler; 完整的代码仍是在Github上了。web

  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格式的文字。spring

结语

ViewResolverResponseBody都是用来处理HttpResponseBody的内容的,只不过处理的方式不一样。 ResponseBody的编码方式是同样的,通常是处理JSON的编码。ViewResolver还能够一根据ViewName来路由到不用的View,每一个View均可以本身的编码规则。app

相关文章
相关标签/搜索