SpringMVC之源码分析--ViewResolver(四)
概述
本章继续学习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,共同学习、共同进步,谢谢!