通常一个web应用中,资源都在浏览器中以HTML的形式表述。
但对于一个RESTful的资源应以多种形式表述,用户要求什么就以什么样的形式表述。
比如HTML、XML、JSON、PDF、EXCEL...
Spring有两种方式处理表述形式:
·org.springframework.web.servlet.view.ContentNegotiatingViewResolver
·org.springframework.http.converter.HttpMessageConverter
一、ContentNegotiatingViewResolver
虽然也是ViewResolver一族的,但他并不解析视图,而是解析视图的任务委托给其他ViewResolver。
默认情况下会从所有ViewResolver中自动选择合适的ViewResolver,但为了更明确的表述,我们会选择使用setViewResolvers(List<ViewResolver> viewResolvers)更明确地定义ViewResolver。
ContentNegotiatingViewResolver委托所有viewResolvers解析视图后将视图存放在待选视图列表(candidateViews)中,如果设置了defaultView,默认视图会在列表的尾部。
如何确定请求的媒体类型? 虽然accept header里也可以获得请求的表述形式,但ContentNegotiatingViewResolver会先考虑URL后面的扩展名。
从扩展名无法得到类型时将考虑accept header。如果此时仍然无法获得有效的类型,将使用defaultContentType设置(先已在ContentNegotiatingViewResolver中deprecated,可以在org.springframework.web.accept.ContentNegotiationManagerFactoryBean中看到)。
我们可以自定义扩展名和媒体类型映射。
看一些书和一些博客直接使用了ContentNegotiatingViewResolver的setMediaTypes(Map<String, String> mediaTypes),但值得注意的是,这个方法已经deprecated了(当然,与之有关的一系列setFavorPathExtension、setFavorParameter、setIgnoreAcceptHeader什么的都deprecated了,但他们都移到了ContentNegotiationManager中)。
现在建议使用setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager),但并不复杂,只是变得更加OO而已。
另外org.springframework.web.accept.ContentNegotiationManagerFactoryBean是ContentNegotiationManager的:
A factory providing convenient access to a {@code ContentNegotiationManager} configured with one or more {@link ContentNegotiationStrategy} instances.
配置contentNegotiationMananger:
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean"> <property name="favorParameter" value="true" /> <property name="parameterName" value="format" /> <property name="ignoreAcceptHeader" value="false" /> <property name="mediaTypes"> <value> json=application/json xml=application/xml </value> </property> <property name="defaultContentType" value="text/html" /> </bean>
话说favorPathExtension默认就是true,因此在这里就不做设置了。
favorPathExtension为false则勿略扩展名。
favorParameter与parameterName是一对属性。
favorParameter设置为true可以用[?format=表述形式]这种方式。
默认值是format,这个值可以通过parameterName来设置。
ignoreAcceptHeader默认为false,即不忽略Accept信息。
配置ContentNegotiatingViewResolver:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name= "contentNegotiationManager" ref= "contentNegotiationManager"/> <!-- <property name="mediaTypes"> --> <!-- <map> --> <!-- <entry key="atom" value="application/atom+xml" /> --> <!-- <entry key="html" value="text/html" /> --> <!-- <entry key="json" value="application/json" /> --> <!-- </map> --> <!-- </property> --> <property name="viewResolvers"> <list> <!-- <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" /> --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> </list> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" /> </list> </property> </bean>
ContentNegotiatingViewResolver的setMediaTypes没deprecated时是想上面那样写的,现在都写到contentNegotiationMananger了。
二、HttpMessageConverter
通常可以看到把对象放到Model中在View中渲染。使用HttpMessageConverter则可以将方法返回的对象转为要求的表述形式。
上面是实现类,很多都是自动注册的。
见org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) { StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); stringConverter.setWriteAcceptCharset(false); messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(stringConverter); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new SourceHttpMessageConverter<Source>()); messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { messageConverters.add(new AtomFeedHttpMessageConverter()); messageConverters.add(new RssChannelHttpMessageConverter()); } if (jaxb2Present) { messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); } if (jackson2Present) { messageConverters.add(new MappingJackson2HttpMessageConverter()); } else if (jacksonPresent) { messageConverters.add(new org.springframework.http.converter.json.MappingJacksonHttpMessageConverter()); } }
另外我们需要用到@ResponseBody,以表示Controller的返回值会绑定在响应体中,并将其转换为客户端请求的表述形式。
请求的表述形式主要是Accept头信息,如果请求中不包括Accept头信息则会假设可以接受任何表述形式。当然,如果在方法的@RequestMapping中设置了Accept头信息则只会处理包含这些Accept信息的请求。
比如下面的代码中我要将User数组转为JSON形式表述:
@RequestMapping(value ="/userList",headers={"Accept="+MediaType.APPLICATION_JSON_VALUE}) public @ResponseBody User[] queryUserList(){ User[] u = new User[4]; u[0] = new User("0001","King","t;stmdtkg"); u[1] = new User("0001","King","t;stmdtkg"); u[2] = new User("0001","King","t;stmdtkg"); u[3] = new User("0001","King","t;stmdtkg"); return u; }
与@ResponseBody对应,我们也可以使用@RequestBody。
比如client端请求时以JSON格式传递参数,我在Controller方法的某个参数前加上@ResponseBody User user则转换为User类型。
另外,注册一个MessageConverter的方式非常简单。
<mvc:annotation-driven> <mvc:message-converters> <bean class="pac.king.common.MessageConverter" /> </mvc:message-converters> </mvc:annotation-driven>
【SpringMVC】根据请求处理资源表述