如何使用?
我们已经知道,默认情况下Spring MVC可没有使用此内容协商视图解析器,因此若有同一资源,多视图展示的需求,我们是需要手动配置(开启)支持的。
通过检索可以看到ViewResolverRegistry它为我们提供了便捷使用的方式:
当然你也可以通过单独配置一个ContentNegotiatingViewResolver @Bean的方式来做,原理也很简单很好解释。本文我就给个最佳实践作为参考示例
public class ViewResolverRegistry { ... public void enableContentNegotiation(View... defaultViews) { initContentNegotiatingViewResolver(defaultViews); } public void enableContentNegotiation(boolean useNotAcceptableStatus, View... defaultViews) { ContentNegotiatingViewResolver vr = initContentNegotiatingViewResolver(defaultViews); vr.setUseNotAcceptableStatusCode(useNotAcceptableStatus); } // 初始化一个内容协商视图解析器 private ContentNegotiatingViewResolver initContentNegotiatingViewResolver(View[] defaultViews) { // ContentNegotiatingResolver in the registry: elevate its precedence! // 请保证它是最高优先级的:在所有视图解析器之前执行 // 这样即使你配置了其它的视图解析器 也会先执行这个(后面的被短路掉) this.order = (this.order != null ? this.order : Ordered.HIGHEST_PRECEDENCE); // 调用者自己已经配置好了一个contentNegotiatingResolver,那就用他的 if (this.contentNegotiatingResolver != null) { // 若存在defaultViews,那就处理一下把它放进contentNegotiatingResolver里面 if (!ObjectUtils.isEmpty(defaultViews) && !CollectionUtils.isEmpty(this.contentNegotiatingResolver.getDefaultViews())) { List<View> views = new ArrayList<>(this.contentNegotiatingResolver.getDefaultViews()); views.addAll(Arrays.asList(defaultViews)); this.contentNegotiatingResolver.setDefaultViews(views); } } else { // 若没配置就自己new一个 并且设置好viewResolvers this.contentNegotiatingResolver = new ContentNegotiatingViewResolver(); this.contentNegotiatingResolver.setDefaultViews(Arrays.asList(defaultViews)); // 注意:这个viewResolvers是通过此ViewResolverRegistry配置进来的 // 若仅仅是容器内的Bean,这里可捕获不到。所以如果你有特殊需求建议你自己set // 若仅仅是jsp()/tiles()/freeMarker()/groovy()/beanName()这些,内置的支持即可满足要求儿聊 // ViewResolverRegistry.viewResolver()可调用多次,因此可以多次指定 若有需要个性化,可以调用此方法 this.contentNegotiatingResolver.setViewResolvers(this.viewResolvers); if (this.contentNegotiationManager != null) { this.contentNegotiatingResolver.setContentNegotiationManager(this.contentNegotiationManager); } } return this.contentNegotiatingResolver; } }
说明一点:虽然这里有些视图解析器是new出来的,但不用担心最后都会执行InitializingBean、ApplicationContextAware…等等的一些接口方法的。因为这些都是交给ViewResolverComposite统一代劳的~(因此并不需要放进Spring容器里亦可,减少容器的负担也是一种优化)
上面"复习"的时候提到了,Spring MVC准备好ViewResolverRegistry后会回调我们,因此实际使用中可以通过此入口进行配置(最佳实践):
@Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.enableContentNegotiation(); // 开启内容协商视图解析器 } }
在我准备介绍案例时,为了便于对小伙伴对整个内容协商流程的把控和理解,我提供如下这张执行原理流程图作为辅助理解(若图有错误可留言指出,多谢):
使用示例
是骡子是马,总归还是要拉出来溜溜。下面我用一个工作中非常具象的案例,来演示一下它的用法。
需求:同一个RESTful的URL,我希望得到一个PDF视图、JSON视图、Html视图???
实现代码
因为是同一个URL,并且还要求是有不同视图的,因此这里用ContentNegotiatingViewResolver来做内容协商就非常得心应手了。
1、准备针对于处理这三种视图的ViewResolver实现类:
// 自定义三个视图分别用于处理对应的视图需求 private final ViewResolver pdf_viewresolver= (viewName, locale) -> new View() { @Override public String getContentType() { return MediaType.APPLICATION_PDF_VALUE; } @Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().write("<html><body style='color:red'>this is pdf view</body></html>"); } }; private final ViewResolver excel_viewresolver= (viewName, locale) -> new View() { @Override public String getContentType() { return MediaType.APPLICATION_JSON_UTF8_VALUE; } @Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().write("<html><body style='color:yellow'>this is json view</body></html>"); } }; private final ViewResolver html_viewresolver= (viewName, locale) -> new View() { @Override public String getContentType() { return MediaType.TEXT_HTML_VALUE; } @Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().write("<html><body style='color:green'>this is html view</body></html>"); } };
请注意:三者的getContentType()、渲染内容、颜色都是不一样的
说明:因为此处我只是模拟,所以我全部以匿名类来实现,各位小伙伴理解起来理论上应该都没有啥障碍吧(有问题可给我留言~)
2、开启Spring MVC在视图上对ContentNegotiation内容协商的支持:
@Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.viewResolver(pdf_viewresolver); registry.viewResolver(excel_viewresolver); registry.viewResolver(html_viewresolver); // 上面三个注册方法必须在此方法之上执行 registry.enableContentNegotiation(false); } }