本文依据个人理解,摘取文章精华部分。
原文:http://m.bianceng.cn/Programming/Java/201110/30861_2.htm
Java EE 6 引入了对 JSR-311 的支持。JSR-311(JAX-RS:Java API for RESTful Web Services)旨在定义一个统一的规范,使得 Java 程序员可以使用一套固定的接口来开发 REST 应用,避免了依赖于第三方框架。同时,JAX-RS 使用 POJO 编程模型和基于标注的配置,并集成了 JAXB,从而可以有效缩短 REST 应用的开发周期。
JAX-RS 的具体实现由第三方提供,例如 Sun 的参考实现 Jersey、Apache 的 CXF 、Axis2 以及 JBoss 的 RESTEasy。
1.不同点
JAX-RS的目标是Web Services开发(这与HTML Web应用不同)而Spring MVC的目 标则是Web应用开发
2.REST与Spring
REST特性是Spring Framework的一部分,也是现有的Spring MVC编 程模型的延续,因此,并没有所谓的“Spring REST framework”这种概念,有的只是Spring和Spring MVC。这意味着如果你有一个Spring应用的话,你既可以使用Spring MVC创建HTML Web层,也可以创建 RESTful Web Services层。
3.相同点
a.持久层使用Spring配置,包含了一个JPA仓储实现,用于获取和持久化实 体实例。Jersey和Spring MVC用于构建Web Services层,通过调用底层的Spring托管应用来服务客户端请 求。
b.Jersey和Spring MVC都使用Spring的ContextLoaderListener加载业务与持久层组件(ContextLoaderListener可用于任何Web或REST框架环境中。)
contextConfigLocation
classpath:META-INF/spring/module-config.xml
org.springframework.web.context.ContextLoaderListener
4.在Jersey中创建Spring管理的JAX-RS资源
Jersey支持在REST层中使用Spring,两个简单的步骤就能搞定(事实上有3步,还需要将构建依赖加到 maven artifact com.sun.jersey.contribs:jersey-spring中)。
步骤一:将如下配置片段加到web.xml中以保证Spring能够创建JAX-RS根资源:
Jersey Web Application
com.sun.jersey.spi.spring.container.servlet.SpringServlet
Jersey Web Application
/resources/*
步骤二:使用Spring和JAX-RS注解声明根JAX-RS资源类:
@Path("/accounts/")
@Component
@Scope("prototype")
public class AccountResource {
@Context
UriInfo uriInfo;
@Autowired
private AccountRepository accountRepository;
}
如下是对这些注解的说明:
AX-RS提供了一些标注将一个资源类,一个POJOJava类,封装为Web资源。标注包括:
@Path,JAX-RS有“根”资源(标记为@Path)和子资源的概念。@Path注释被用来描述根资源、子资源方法或子资源的位置。value 值可以包含文本字符、变量或具有定制正则表达式的变量。清单 6 的例子展示了 @Path 注释的主要应用。在上面的示例中,AccountResource就是个根 资源,它会处理以 “/accounts/”开头的路径。AccountResource中的方法如getAccount()只需声明针对 类型级别的相对路径即可。
@GET,@PUT,@POST,@DELETE,标注方法是用的HTTP请求的类型.您可以使用它们来绑定根资源或子资源内的 Java 方法与 HTTP 请求方法。HTTP GET 请求被映射到由 @GET 注释的方法;HTTP POST 请求被映射到由 @POST 注释的方法,以此类推。用户可能还需要通过使用 @HttpMethod 注释定义其自己的定制 HTTP 请求方法指示符。
@Produces,注释代表的是一个资源可以返回的 MIME 类型。这些注释均可在资源、资源方法、子资源方法、子资源定位器或子资源内找到。
@Consumes,注释代表的是一个资源可以接受的 MIME 类型。
@PathParam,@QueryParam,@HeaderParam,@CookieParam,@MatrixParam,@FormParam,分别标注方法的参数来自于HTTP请求的不同位置,例如@PathParam来自于URL的路径,@QueryParam来自于URL的查询参数,@HeaderParam来自于HTTP请求的头信息,@CookieParam来自于HTTP请求的Cookie。
@Component将AccountResource声明为Spring bean。
@Scope声明了一个prototype Spring bean,这样每次使用时都会实例化(比如每次请求时)。
@Autowired指定了一个AccountRepository引用,Spring会提供该引用。
@Context也是一个JAX-RS注解,要求注入特定于请求的UriInfo对象。
@Path("/accounts/")
@Component
@Scope("prototype")
public class AccountResource {
@GET
@Path("{username}")
public Account getAccount(@PathParam("username") String username) {
}
}
访问路径“/accounts/{username}”(其中的username是路径参数,可以是某个账户的用户名)的请 求将由getAccount()方法处理。
根资源由JAX-RS运行时(在本示例中是Spring)实例化,子资源则由应用本身实例化。比如说,对于 “/accounts /{username}/portfolios/{portfolioName}”这样的请求,AccountResource(由路径的第 一部分“ /accounts”标识)会创建一个子资源实例,请求会被代理给该实例:
@Path("/accounts/")
@Component
@Scope("prototype")
public class AccountResource {
@Path("{username}/portfolios/")
public PortfolioResource getPortfolioResource(@PathParam("username") String username) {
return new PortfolioResource(accountRepository, username, uriInfo);
}
}
PortfolioResource本身的声明并没有使用注解,因此其所有的依赖都是由父资源传递过来的:
public class PortfolioResource {
private AccountRepository accountRepository;
private String username;
private UriInfo uriInfo;
public PortfolioResource(AccountRepository accountRepository, String username, UriInfo uriInfo) {
this.accountRepository = accountRepository;
this.username = username;
this.uriInfo = uriInfo;
}
}
请记住,资源类是Web Services层组件,应当关注于Web Services相关的处理,比如输入转换、准备 响应、设定响应代码等等。此外,将Web Services逻辑与业务逻辑分隔开来的实践需要将业务逻辑包装到 单独的方法中以作为事务边界。
5.创建Spring MVC @Controller类
对于Spring MVC来说,我们需要创建DispatcherServlet,同时将contextConfigLocation参数指定为 Spring MVC配置:
Spring MVC Dispatcher Servlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
/WEB-INF/spring/*.xml
要想在Spring MVC(@MVC)中使用基于注解的编程模型还需要少量的配置。下面的component-scan元 素会告诉Spring去哪里寻找@Controller注解类。
接下来,我们声明了AccountController,如下代码所示:
@Controller
@RequestMapping("/accounts")
public class AccountController {
@Autowired
private AccountRepository accountRepository;
}
@RequestMapping注解会将该控制器映射到所有以“/accounts”开头的请求上。AccountController中 的方法如getAccount()只需声明针对“/accounts”的相对地址即可。
@RequestMapping(value = "/{username}", method = GET)
public Account getAccount(@PathVariable String username) {
}
Spring MVC则没有根资源与子资源的概念,这样每个控制器都是由Spring而非应用来管理的
6.Web层组件范围
在JAX-RS中,AccountResource是通过前请求(per-request)语义声明的,这也是JAX-RS默认的推荐 设置。这么做可以将特定于请求的数据注入并存储到资源类本身当中,这适用于由JAX-RS所管理的根级别 资源。子资源由应用实例化,并不会直接从这种方法中获益。
在Spring MVC中,控制器永远都是单例的,他们将特定于请求的数据作为方法参数。JAX-RS也可以这 么做,以单例的方式创建资源。
7.将请求映射到方法上
接下来,我们看看Spring MVC和JAX-RS如何将请求映射到方法上。@Path和@RequestMapping都可以从 URL中抽取出路径变量:
@Path("/accounts/{username}")
@RequestMapping("/accounts/{username}")
这两个框架也都可以使用正则表达式抽取路径变量:
@Path("/accounts/{username:.*}")
@RequestMapping("/accounts/{username:.*}"
Spring MVC的@RequestMapping可以根据查询参数的有无来匹配请求:
@RequestMapping(parameters="foo")
@RequestMapping(parameters="!foo")
或是根据查询参数值进行匹配:
@RequestMapping(parameters="foo=123")
@RequestMapping还可以根据头信息的有无来匹配请求:
@RequestMapping(headers="Foo-Header")
@RequestMapping(headers="!Foo-Header")
或是根据头信息的值进行匹配:
@RequestMapping(headers="content-type=text/*")
Spring MVC和JAX-RS拥有能够抽取这种HTTP请求值的注解:@GET @Path
public void foo(@QueryParam("q") String q, @FormParam("f") String f, @CookieParam ("c") String c,
@HeaderParam("h") String h, @MatrixParam("m") m) {
// JAX-RS
}
@RequestMapping(method=GET)
public void foo(@RequestParam("q") String q, @CookieValue("c") String c, @RequestHeader("h") String h) {
// Spring MVC
}
上面的注解非常像,区别在于JAX-RS支持矩阵参数(matrix parameters)的抽取,拥有单独的注解来 处理查询字符串和表单参数。矩阵参数并不常见,他们类似于查询字符串参数,但却使用了特殊的路径片 段(比如GET /images;name=foo;type=gif)。稍后将介绍表单参数。
假如使用了前请求范围声明资源,那么JAX-RS可以在属性和setters方法上使用上述注解。
Spring MVC有个特性能让我们少敲几个字符,如果注解名与Java参数名相同,那么就可以省略掉上面 的注解名了。比如说,名为“q”的请求参数要求方法参数也得为“q”:
public void foo(@RequestParam String q, @CookieValue c, @RequestHeader h) {
}
这对于那些在参数中使用了注解而导致方法签名变长的情况来说实在是太方便了。请记住,这个特性 要求代码使用调试符号进行编译。
8.类型转换与HTTP请求值的格式化
HTTP请求值(头、cookies和参数)是不变的字符串并且需要解析。
JAX-RS通过寻找valueOf()方法或是在客户化的目标类型中接收字符串的构造方法来解析请求数据。 JAX-RS支持如下类型的注解方法参数,包括路径变量、请求参数、HTTP头值和cookies:
原生类型。
拥有接收单个字符串参数的构造方法的类型。
拥有一个接收单个字符串参数的名为valueOf的静态方法的类型。
List
Spring 3支持上面所有要求。除此之外,Spring 3提供了一种全新的类型转换与格式化机制,并且可 以使用注解实现。
9.表单数据
如前所述,JAX-RS处理查询字符串参数和表单参数的方式是不同的。虽然Spring MVC只有一个 @RequestParam,但它还提供了一种Spring MVC用户很熟悉的数据绑定机制来处理表单输入。
比如说,如果一个表单提交了3个数据,那么一种可能的处理方式就是声明一个带有3个参数的方法:
@RequestMapping(method=POST)
public void foo(@RequestParam String name, @RequestParam creditCardNumber, @RequestParam expirationDate) {
Credit card = new CreditCard();
card.setName(name);
card.setCreditCardNumber(creditCardNumber);
card.setExpirationDate(expirationDate);
}
@RequestMapping(method=POST)
public void foo(CreditCard creditCard) {
// POST /creditcard/1
// name=Bond
// creditCardNumber=1234123412341234
// expiration=12-12-2012
}
要想与Web浏览器协同工作,表单处理是个重要环节。另一方面,Web Services客户端一般会在请求体 中提交XML或JSON格式的数据。
处理请求体中的数据
无论是Spring MVC还是JAX-RS都能够自动处理请求体中的数据:
@POST
public Response createAccount(Account account) {
// JAX_RS
}
@RequestMapping(method=POST)
public void createAccount(@RequestBody Account account) {
// Spring MVC
}
在Spring MVC中,如果想通过请求体数据初始化方法参数,那可以将@RequestBody注解加到该方法参 数前,这与之前介绍的表单参数初始化正好相反。
在Spring MVC中,HttpMessageConverter类负责转换请求体数据,Spring MVC提供了一个开箱即用的 Spring OXM HttpMessageConverter。它支持JAXB、Castor、JiBX、XMLBeans和XStream,此外还有一个用 于处理JSON的Jackson HttpMessageConverter。
HttpMessageConverter会注册到AnnotationMethodHandlerAdapter上,后者会将到来的请求映射到 Spring MVC @Controllers上。下面是其配置:
Spring 3新增的mvc客户化命名空间将上述配置自动化了,只需增加如下配置片段即可:
如果JAXB位于类路径上,它会注册一个用于读写XML的转换器;如果Jackson位于类路径上,它会注册 一个用于读写JSON的转换器。
准备响应
典型的响应需要准备响应代码、设定HTTP响应头、将数据放到响应体当中,还需要处理异常。
11.使用JAX-RS设定响应体数据
在JAX-RS中,要想将数据加到响应体中,只需要从资源方法中返回对象即可:
@GET
@Path("{username}")
public Account getAccount(@PathParam("username") String username) {
return accountRepository.findAccountByUsername(username);
}
JAX-RS会寻找类型MessageBodyWriter的实体供应者,它能将对象转换为所需的内容类型。JAX-RS实现 需要具备一个JAXB MessageBodyWriter,这可以使用具有注解@Provider的客户化MessageBodyWriter实现 。
使用Spring MVC设定响应体数据
在Spring MVC中,响应是通过一个视图解析过程来实现的,这样就可以从一系列视图技术中选择了。 但在与Web Services客户端交互时,更加合理的方式则是舍弃视图解析过程,转而使用方法所返回的对象 :
@RequestMapping(value="/{username}", method=GET)
public @ResponseBody Account getAccount(@PathVariable String username) {
return accountRepository.findAccountByUsername(username);
}
如果对控制器方法或其返回类型应用注解@ResponseBody,那么就会使用HttpMessageConverter处理返 回值,然后用该返回值设定响应体。用于请求体参数的HttpMessageConverter集合也用于响应体,因此无 需再做任何配置。
状态代码与响应头
JAX-RS使用一个链式API来构建响应:
@PUT @Path("{username}")
public Response updateAccount(Account account) {
// ...
return Response.noContent().build(); // 204 (No Content)
}
@POST
public Response createAccount(Account account) {
// ...
URI accountLocation = uriInfo.getAbsolutePathBuilder().path(account.getUsername ()).build();
return Response.created(accountLocation).build();
}
上面代码中所用的uriInfo要么被注入到根资源(使用了@Context)中,要么是从父资源传递给子资源 。它可以附加到当前请求的路径之后。
Spring MVC提供了一个注解来设定响应代码:
@RequestMapping(method=PUT)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void updateAccount(@RequestBody Account account) {
// ...
}
@RequestMapping(method=POST)
@ResponseStatus(CREATED)
public void createAccount(@RequestBody Account account, HttpServletRequest request,
HttpServletResponse response) {
// ...
String requestUrl = request.getRequestURL().toString();
URI uri = new UriTemplate("{requestUrl}/{username}").expand(requestUrl, account.getUsername());
response.setHeader("Location", uri.toASCIIString());
}
异常处理
JAX-RS允许资源方法抛出WebApplicationException类型的异常,该异常会包含一个响应。下面的示例 代码将一个JPA NoResultException转换为特定于Jersey的NotFoundException,这会导致一个404的错误 :
@GET
@Path("{username}")
public Account getAccount(@PathParam("username") String username) {
try {
return accountRepository.findAccountByUsername(username);
} catch (NoResultException e) {
throw new NotFoundException();
}
}
WebApplicationException实例会封装必要的逻辑来生成特定的响应,但每个独立的资源类方法中都需 要捕获异常。
Spring MVC支持定义控制器级别的方法来处理异常:
@Controller
@RequestMapping("/accounts")
public class AccountController {
@ResponseStatus(NOT_FOUND)
@ExceptionHandler({NoResultException.class})
public void handle() {
// ...
}
}
如果任何控制器方法抛出了JPA的NoResultException异常,上面的处理器方法就会得到调用并处理该 异常,然后返回一个404错误。这样,每个控制器就都能处理异常了,好象来自同一个地方一样。
总结
希望本文有助于你理解Spring MVC构建RESTful Web Services的方式及其与JAX-RS编程模型之间的异 同点。
如果你是个Spring MVC用户,那么你可能用它开发过HTML Web应用了。REST概念适用于Web Services 和Web应用,尤其是富客户端交互上更是如此。除了本文介绍的特性之外,Spring 3还增加了对RESTful Web应用的支持。这是部分新特性的列表:用于从URL模板构建URL的新的JSP客户化标签、基于HTTP PUT和 DELETE模拟表单提交的Servlet过滤器、根据内容类型自动选择视图的 ContentTypeNegotiatingViewResolver、新的视图实现等等。此外,Spring文档也改进颇多。
关于作者
Rossen Stoyanchev是SpringSource的高级咨询师。在其职业生涯中,他做过贸易应用、记账系统和电 子商务等Web应用。