使用Spring MVC编写RESTful Controller

1、Spring对REST的支持

Spring3(这里讨论Spring3.2+)对Spring MVC的一些增强功能为REST提供了良好的支持。Spring对开发REST资源提供以下支持:

  1. 操作方式:控制器可以处理所有的HTTP方法,包含4个主要的REST方法:GET、PUT、DELETE以及POST。Spring的表单绑定JSP标签库的<form:form>标签以及新的HiddenHttpMethodFilter,使得通过HTML表单提交的PUT和DELETE请求成为可能
  2. 资源标识:新的@PathVariable注解使得控制器能够处理参数化的URL
  3. 资源表述方法1:通过使用Spring的视图解析器,资源可以以各种形式进行表述,包括XML、JSON、Atom和RSS()。使用新的ContentNegotiatingViewResolver来选择最合适客户端的表述
  4. 资源表述方法2:通过使用@ResponseBody注解和各种HttpMethodConverter实现来达到。@RequestBody注解记忆HttpMethodConverter实现可以将传入的HTTP数据转化为传入控制器方法的Java对象
  5. REST客户端:RestTemplate简化了客户端对REST资源的使用

 2、参数化URL

Spring3.0中引入了新的@PathVariable注解,帮助Controller使用面向资源的方式处理请求。如:

 1 @Controller
 2 @RequestMapping(value="/spitters")
 3 public class SpittleController{
 4     private SpitterService spitterService;
 5     ...
 6     @RequestMapping(value="/{id}", method= RequestMethod.PUT)
 7     @ResponseStatus(HttpStatus.NO_CONTENT)
 8     public void putSpittle(@PathVariable("id")) long id,
 9                            @Valid Spittle spittle)
10     {
11         spitterService.saveSpittle(spittle);
12     }
13 }

3、协商资源表述

“协商资源表述”即确定一个HTTP请求所需要返回的资源格式,HTML、XML还是JSON等。按优先级,Spring MVC依次通过以下三个条件确定客户端需要什么类型的内容表述:

  1. URL路径上添加扩展名,如 http://myserver/myapp/accounts/list.html 或者 http://myserver/myapp/accounts/list.xls。
  2. URL参数,如 http://myserver/myapp/accounts/list?format=xls,参数名默认为“format”,但也可以更改。
  3. 通过HTTP请求的Accept头部信息确定返回资源格式,但由于客户端的处理不同,这个方法并不太可靠。

JAF(JavaBeans Activation Framework)可提供由扩展名(或URL参数)到资源格式的自动映射机制(如json->"application/json"),这时classpath下必须有activation.jar包。

Spring MVC通过ContentNegotiationManager来进行资源协商,一般配置如下:

 1 <bean id="contentNegotiationManager"
 2       class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
 3 <property name="favorPathExtension" value="false" />
 4 <property name="favorParameter" value="true" />
 5 <property name="parameterName" value="mediaType" />
 6 <property name="ignoreAcceptHeader" value="true"/>
 7 <property name="useJaf" value="false"/>
 8 <property name="defaultContentType" value="application/json" />
 9 
10 <property name="mediaTypes">
11     <map>
12         <entry key="json" value="application/json" />
13         <entry key="xml" value="application/xml" />
14     </map>
15 </property>
16 </bean>

以上配置表示:不使用路径扩展名方式,也不使用Accept头信息方式,仅使用URL参数方式确定资源类型,URL参数名使用“mediaType”代替默认的“format”,不使用JAF而是自行定义URL参数到资源格式的映射,这里只定义了JSON和XML。

4、表述资源(一)

Spring MVC通过两种方式将Java表述形式的资源转化为发送给客户端的表述形式:

  • 基于视图渲染进行协商(@ContentNegotiatingViewResolver)
  • HTTP消息转换器(HttpMessageConverters)

首先是第一种方式,使用HttpMessageConverters表述资源。当ContentNegotiationManager配置如下,且类路径下包含JAXB和Jackson包时,Spring MVC将自动匹配使用哪种HttpMessageCoverter。

ContentNegotiationManager配置:

 1 <!-- enable the "produces" annotation of "RequestMapping" -->
 2 <mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
 3 
 4 <!-- Simple strategy: only path extension is taken into account -->
 5 <bean id="cnManager"
 6       class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
 7 <property name="favorPathExtension" value="true"/>
 8 <property name="ignoreAcceptHeader" value="true" />
 9 <property name="defaultContentType" value="text/html" />
10 <property name="useJaf" value="false"/>
11 
12 <property name="mediaTypes">
13     <map>
14         <entry key="html" value="text/html" />
15         <entry key="json" value="application/json" />
16         <entry key="xml" value="application/xml" />
17     </map>
18 </property>
19 </bean>

Controller:

 1 @Controller
 2 class AccountController {
 3     // RESTful method, use MappingJacksonHttpMessageConverter and Jaxb2RootElementHttpMessageConverter automatically
 4     //accounts.json -> application/json
 5     //accounts.xml -> text/xml
 6     @RequestMapping(value="/accounts", produces={"application/xml", "application/json"})
 7     @ResponseStatus(HttpStatus.OK)
 8     public @ResponseBody List<Account> listWithMarshalling(Principal principal) {
 9         return accountManager.getAccounts(principal);
10     }
11 
12     // View-based method
13     //accounts.html -> text/html
14     //accounts.others -> text/html
15     @RequestMapping("/accounts")
16     public String listWithView(Model model, Principal principal) {
17         // Call RESTful method to avoid repeating account lookup logic
18         model.addAttribute( listWithMarshalling(principal) );
19 
20         // Return the view to use for rendering the response
21         return ¨accounts/list¨;
22     }
23 }

 使用 JAXB 和 Jackson时,需要在account类上添加annotation:

技术分享
 1 /**
 2  * Represents an account for a member of a financial institution. An account has
 3  * zero or more {@link Transaction}s and belongs to a {@link Customer}. An aggregate entity.
 4  */
 5 @Entity
 6 @Table(name = "T_ACCOUNT")
 7 @XmlRootElement
 8 public class Account {
 9 
10     // data-members omitted ...
11 
12     public Account(Customer owner, String number, String type) {
13         this.owner = owner;
14         this.number = number;
15         this.type = type;
16     }
17 
18     /**
19      * Returns the number used to uniquely identify this account.
20      */
21     @XmlAttribute
22     public String getNumber() {
23         return number;
24     }
25 
26     /**
27      * Get the account type.
28      *
29      * @return One of "CREDIT", "SAVINGS", "CHECK".
30      */
31     @XmlAttribute
32     public String getType() {
33         return type;
34     }
35 
36     /**
37      * Get the credit-card, if any, associated with this account.
38      *
39      * @return The credit-card number or null if there isn‘t one.
40      */
41     @XmlAttribute
42     public String getCreditCardNumber() {
43         return StringUtils.hasText(creditCardNumber) ? creditCardNumber : null;
44     }
45 
46     /**
47      * Get the balance of this account in local currency.
48      *
49      * @return Current account balance.
50      */
51     @XmlAttribute
52     public MonetaryAmount getBalance() {
53         return balance;
54     }
55 
56 
57     /**
58      * Returns a single account transaction. Callers should not attempt to hold
59      * on or modify the returned object. This method should only be used
60      * transitively; for example, called to facilitate reporting or testing.
61      *
62      * @param name
63      *            the name of the transaction account e.g "Fred Smith"
64      * @return the beneficiary object
65      */
66     @XmlElement   // Make these a nested <transactions> element
67     public Set<Transaction> getTransactions() {
68         return transactions;
69     }
70 
71     // Setters and other methods ...
72 
73 }
View Code

5、表述资源(二) 

第二种方式,使用视图渲染器(View Resolution)。使用ContentNegotiatingViewResolver和ContentNegotiationManager进行配置:

 1 <!-- View resolver that delegates to other view resolvers based on the content type -->
 2 <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
 3 <!-- All configuration is now done by the manager - since Spring V3.2 -->
 4     <property name="contentNegotiationManager" ref="cnManager"/>
 5 </bean>
 6 
 7 <!-- Setup a simple strategy -->
 8 <bean id="cnManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
 9     <property name="ignoreAcceptHeader" value="true"/>
10     <property name="defaultContentType" value="text/html" />
11 </bean>

使用以上配置Spring将根据约定自动查找ViewResolvers,并渲染资源。也可显式配置ViewResolvers:

 1 <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
 2 <property name="contentNegotiationManager" ref="cnManager"/>
 3 
 4 <!-- Define the view resolvers explicitly -->
 5 <property name="viewResolvers">
 6     <list>
 7         <bean class="org.springframework.web.servlet.view.XmlViewResolver">
 8             <property name="location" value="spreadsheet-views.xml"/>
 9         </bean>
10 
11         <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
12             <property name="prefix" value="WEB-INF/views"/>
13             <property name="suffix" value=".jsp"/>
14         </bean>
15     </list>
16 </property>
17 </bean>

这样Controller就可以去掉@ResponseBody的Method,只写一个Method了。

6、参考

Content Negotiation using Views

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。