前言
上一篇文章介紹了SpringMvc的ControllerAdvice和ExceptionHandler提岔,這里在介紹一下ViewResolver的使用,并介紹一下HandlerMethodReturnValueHandler和ViewResolver的關(guān)系笋敞。
ViewResolver和HandlerMethodReturnValueHandler
自定義ResponseBody這篇文章介紹過ResponseBody的編碼規(guī)則碱蒙,ViewResolver和ResponseBody是明顯互斥的。
這兩個(gè)不同類型的返回值就是通過不同的HandlerMethodReturnValueHandler來是實(shí)現(xiàn)的夯巷。
先看RequestResponseBodyMethodProcessor振亮,這里設(shè)置了setRequestHandled為true,然后通過HttpMessageConverters編碼對(duì)應(yīng)的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坊秸,這里沒有設(shè)置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喷面,后面就不會(huì)調(diào)用ViewResolver了星瘾。假如是false,會(huì)取出mavContainer.getViewName惧辈,返回ModelAndView琳状,后面會(huì)根據(jù)ViewName進(jìn)行模板的映射。
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就比較簡單了念逞,只要注入一個(gè)ViewResolver的實(shí)現(xiàn)類就可以了。
不過為了介紹ViewResolver的原理边翁,這里自定義了一個(gè)HandlerMethodReturnValueHandler來取代ViewNameMethodReturnValueHandler;
完整的代碼還是在Github上了翎承。
- 定義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;
}
}
- 注入自定義的ViewResolvers和HandlerMethodReturnValueHandler
@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());
}
}
- 自定義HandlerMethodReturnValueHandler叨咖,指定只支持ViewName這個(gè)類,處理時(shí)啊胶,取出來ViewName的name甸各,設(shè)置到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());
}
}
- 自定義ViewResolver焰坪,定義了MyView和MyHtmlView一個(gè)處理index返回純文字趣倾,另一個(gè)處理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());
}
}
- 程序入口琳彩,這里同時(shí)請(qǐng)求了/和/html分別用到了MyView和MyHtmlView誊酌。
@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()));
}
}
運(yùn)行程序就會(huì)發(fā)現(xiàn),一個(gè)輸出了文章露乏,另一個(gè)輸出了html格式的文字碧浊。
結(jié)語
ViewResolver和ResponseBody都是用來處理HttpResponseBody的內(nèi)容的,只不過處理的方式不同瘟仿。
ResponseBody的編碼方式是一樣的箱锐,一般是處理JSON的編碼。ViewResolver還可以一根據(jù)ViewName來路由到不用的View劳较,每個(gè)View都可以自己的編碼規(guī)則驹止。