概述
本章继续学习ViewResolver另一个实现类ContentNegotiatingViewResolver解析器,该类的主要作用是根据同一请求的某些策略,选择对应的View进行渲染。可以把ContentNegotiatingViewResolver理解为适配器,对不同类型View进行适配。值得注意的是处理的handler为同一个。
ContentNegotiatingViewResolver本身不解析视图,它将委托其他视图解析器进行视图解析。
请求的策略包括:请求后缀、请求头的Accept、使用参数等等。
本系列文章是基于Spring5.0.5RELEASE。
流程概述
使用此视图解析器时,调用resolverViewName(viewName,locale)方法,首先调用本类的getMediaType(reuqest)获取请求的媒体类型mediaType(根据策略),然后调用getCandidateViews()方法解析归并到View集合,最后调用getBestView()方法,根据contentType选择出合适的视图并返回。
源码分析
- ContentNegotiatingViewResolver
该类主要完成
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
implements ViewResolver, Ordered, InitializingBean {
// 判断请求mediaType,内部包含使用的策略集合
@Nullable
private ContentNegotiationManager contentNegotiationManager;
// 用于创建ContentNegotiationManager实例
private final ContentNegotiationManagerFactoryBean cnmFactoryBean = new ContentNegotiationManagerFactoryBean();
// 控制为查找到时的处理
private boolean useNotAcceptableStatusCode = false;
// 存储View实例,可在此集合查询符合条件的View实例进行视图渲染
@Nullable
private List<View> defaultViews;
// 视图解析器集合,用于解析视图
@Nullable
private List<ViewResolver> viewResolvers;
// 排序属性
private int order = Ordered.HIGHEST_PRECEDENCE;
... ...
/**
*启动时从上下文中加载ViewResolver
*/
@Override
protected void initServletContext(ServletContext servletContext) {
// 从上下文中获取所有视图解析器
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
if (this.viewResolvers == null) {
// 将上下文配置的视图解析器添加到属性viewResolvers中,以供后续使用
this.viewResolvers = new ArrayList<>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}
else {
// 初始化viewResolvers属性中配置的视图解析器
for (int i = 0; i < this.viewResolvers.size(); i++) {
ViewResolver vr = this.viewResolvers.get(i);
if (matchingBeans.contains(vr)) {
continue;
}
String name = vr.getClass().getName() + i;
obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
}
}
if (this.viewResolvers.isEmpty()) {
logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the " +
"'viewResolvers' property on the ContentNegotiatingViewResolver");
}
// 排序视图解析器
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
/*
*启动时调用,如果没有配置ContentNegotiationManager,启动时进行创建初始化该属性
*/
@Override
public void afterPropertiesSet() {
if (this.contentNegotiationManager == null) {
this.contentNegotiationManager = this.cnmFactoryBean.build();
}
}
/*
*请求到来时匹配核实的View并返回
*/
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
// 获取请求的mediaType
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
// 解析出所有视图View和配置的默认View合并,供后面从中匹配选择
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
// 根据contentType匹配选择出合适的View
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
// 返回
if (bestView != null) {
return bestView;
}
}
// 未匹配到合适的View,并且把参数useNotAcceptableStatusCode设置为true时,返回406
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code");
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("No acceptable view found; returning null");
return null;
}
... ...
}
以上是ContentNegotiatingViewResolver类的主要代码,具体调用的方法再此不再展开,有兴趣的童鞋可以自行查看,下面我们来使用这个解析器做个例子,通过例子再加深下理解。
实战
- 需求目标
实现后缀名或参数控制,显示不同的视图。如:
http://localhost:8088/user jsp视图显示
http://localhost:8088/user.json(http://localhost:8088/user?format=json) json视图显示
http://localhost:8088/user.xml(http://localhost:8088/user?format=xml) xml视图显示
- 项目结构
新建maven web项目,最终目录结构如下:
- pom文件
通过maven引入jar依赖,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.dalianghe</groupId>
<artifactId>spring-mvc-viewresolver-03</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-mvc-viewresolver-03 Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- 依赖spring mvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- json依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<!--xml依赖-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.5</version>
</dependency>
</dependencies>
<build>
<finalName>spring-mvc-viewresolver-03</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/</path>
<port>8088</port>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
- spring配置文件
配置视图解析器等,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"
default-autowire="byName">
<!-- 扫描指定路径 -->
<context:component-scan base-package="com.github.dalianghe.controller"/>
<!-- 配置ContentNegotiationManagerFactoryBean构造ContentNegotiationManager实例 -->
<bean id="cnManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<!-- 忽略accept header,即禁用HeaderContentNegotiationStrategy策略 -->
<property name="ignoreAcceptHeader" value="true"/>
<!-- 是否启用扩展名支持,即支持PathExtensionContentNegotiationStrategy策略 -->
<property name="favorPathExtension" value="true"></property>
<!-- 是否启用参数支持,即支持ParameterContentNegotiationStrategy策略 -->
<property name="favorParameter" value="true"></property>
<!--<property name="defaultContentType" value="text/html"/>-->
<property name="mediaTypes">
<map>
<entry key="xml" value="application/xml"/>
<!--<entry key="json" value="text/plain"/>-->
<entry key="json" value="application/json"/>
<!--<entry key="xls" value="application/vnd.ms-excel"/>-->
</map>
</property>
</bean>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="contentNegotiationManager" ref="cnManager"/>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
<!-- 默认支持的View -->
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
<!--<property name="prettyPrint" value="true"/>-->
<property name="contentType" value="application/json"/>
</bean>
<bean class="org.springframework.web.servlet.view.xml.MappingJackson2XmlView">
<property name="contentType" value="application/xml"/>
</bean>
</list>
</property>
<!--<property name="useNotAcceptableStatusCode" value="true"/>-->
</bean>
</beans>
- 部署描述文件
配置DispatcherServlet,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<!-- Servlet名称,可任意定义,但必须与servlet-mapping中对应 -->
<servlet-name>dispatcher</servlet-name>
<!-- 指定Spring MVC核心控制类,即J2EE规范中的前端控制器(Front Controller) -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定Spring MVC配置文件,默认在WEB-INF目录下,切名字为[servlet-name]-servlet.xml,此文件中配置web相关内容,比如:指定扫描Controller路径、配置逻辑视图前缀后缀、上传文件等等 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-servlet.xml</param-value>
</init-param>
<!-- 此配置的值为正整数时,表示容器启动时初始化,即调用Servlet的init方法 -->
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<!-- 定义servlet映射 -->
<servlet-mapping>
<!-- 与servlet中servlet-name对应 -->
<servlet-name>dispatcher</servlet-name>
<!-- 映射所有的url -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- User实体
简单的实体类,代码如下:
public class User{
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
- handler处理器
编写Controller,代码如下:
import com.github.dalianghe.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class DemoController {
@GetMapping("/user")
public String demo(ModelMap model){
User user = new User("hedaliang", "123456");
model.addAttribute(user);
return "user";
}
}
- jsp页面
jsp视图(user.jsp),代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>My Frist JSP</title>
</head>
<body>
<h1>username:${user.username}</h1><br>
<h1>password:${user.password}</h1>
</body>
</html>
以上代码编写结束,下面来进行测试。
测试
启动应用,访问地址:http://localhost:8080/user,此时应使用jsp进行渲染,结果如下:
访问http://locahost:8088/user.json或http://localhost:8088/user?format=json,结果如下:
访问http://localhost:8088/user.xml或http://localhost:8088/user?format=xml,结果如下:
OK!跟预期一致,测试通过,至此我们就实现了需求功能。
总结
本章介绍了ContentNegotiatingViewResolver类,并通过开发小demo验证了此类的功能,里面细节很多,有兴趣的朋友可以再深入了解,希望本文能给大家一写启发。
最后创建了qq群方便大家交流,可扫描加入,同时也可加我qq:276420284,共同学习、共同进步,谢谢!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。