导言
\过去几年,REST逐渐成为影响Web框架、Web协议与Web应用设计的重要概念。如果你还不了解REST,那这个简短的介绍将有助你快速掌握REST,此外还可以点击这里了解关于REST的更多信息。
\现在有越来越多的公司希望能以简单而又贴合Web架构本身的方式公开Web API,因此REST变得越来越重要也就不足为奇了。使用Ajax进行通信的富浏览器端也在朝这个目标不断迈进。这个架构原则提升了万维网的可伸缩性,无论何种应用都能从该原则中受益无穷。
\JAX-RS(JSR 311)指的是Java API for RESTful Web Services,Roy Fielding也参与了JAX-RS的制订,他在自己的博士论文中定义了REST。对于那些想要构建RESTful Web Services的开发者来说,JAX-RS给出了不同于JAX-WS(JSR-224)的另一种解决方案。目前共有4种JAX-RS实现,所有这些实现都支持Spring,Jersey则是JAX-RS的参考实现,也是本文所用的实现。
\如果你使用Spring进行开发,那可能想知道(或者有人曾问过你)Spring MVC与JAX-RS有何异同点?更进一步,如果你手头有一个Spring MVC应用,使用了控制类继承(SimpleFormController等),你可能还意识不到现在的Spring MVC对REST广泛的支持。
\本文将介绍Spring 3中的REST特性并与JAX-RS进行对比,希望能帮助你理顺这两种编程模型之间的异同点。
\开始前,有必要指出JAX-RS的目标是Web Services开发(这与HTML Web应用不同)而Spring MVC的目标则是Web应用开发。Spring 3为Web应用与Web Services增加了广泛的REST支持,但本文则关注于与Web Services开发相关的特性。我觉得这种方式更有助于在JAX-RS的上下文中讨论Spring MVC。
\要说明的第二点是我们将要讨论的REST特性是Spring Framework的一部分,也是现有的Spring MVC编程模型的延续,因此,并没有所谓的“Spring REST framework”这种概念,有的只是Spring和Spring MVC。这意味着如果你有一个Spring应用的话,你既可以使用Spring MVC创建HTML Web层,也可以创建RESTful Web Services层。
\关于文中的代码片段
\文中的代码片段假想了一个简单的领域模型:两个JPA注解实体,分别是Account和Portfolio,其中一个Account对应多个Portfolio。持久层使用Spring配置,包含了一个JPA仓储实现,用于获取和持久化实体实例。Jersey和Spring MVC用于构建Web Services层,通过调用底层的Spring托管应用来服务客户端请求。
\引导程序与Web层包装
\我们会在Spring MVC和JAX-RS中都使用Spring实现依赖注入。Spring MVC DispatcherServlet和Jersey SpringServlet会把请求代理给Spring管理的REST层组件(控制器或资源),后者会由业务或持久层组件包装起来,如下图所示:
\Jersey和Spring MVC都使用Spring的ContextLoaderListener加载业务与持久层组件,比如JpaAccountRepository:
\\\u0026lt;context-param\u0026gt;\ \u0026lt;param-name\u0026gt;contextConfigLocation\u0026lt;/param-name\u0026gt;\ \u0026lt;param-value\u0026gt;\ classpath:META-INF/spring/module-config.xml\ \u0026lt;/param-value\u0026gt;\\u0026lt;/context-param\u0026gt;\\\u0026lt;listener\u0026gt;\ \u0026lt;listener-class\u0026gt;\ org.springframework.web.context.ContextLoaderListener\ \u0026lt;/listener-class\u0026gt;\\u0026lt;/listener\u0026gt;\\\
ContextLoaderListener可用于任何Web或REST框架环境中。
\在Jersey中创建Spring管理的JAX-RS资源
\Jersey支持在REST层中使用Spring,两个简单的步骤就能搞定(事实上有3步,还需要将构建依赖加到maven artifact com.sun.jersey.contribs:jersey-spring中)。
\步骤一:将如下配置片段加到web.xml中以保证Spring能够创建JAX-RS根资源:
\\\u0026lt;servlet\u0026gt;\ \u0026lt;servlet-name\u0026gt;Jersey Web Application\u0026lt;/servlet-name\u0026gt;\ \u0026lt;servlet-class\u0026gt;\ com.sun.jersey.spi.spring.container.servlet.SpringServlet\ \u0026lt;/servlet-class\u0026gt;\\u0026lt;/servlet\u0026gt;\\\u0026lt;servlet-mapping\u0026gt;\ \u0026lt;servlet-name\u0026gt;Jersey Web Application\u0026lt;/servlet-name\u0026gt;\ \u0026lt;url-pattern\u0026gt;/resources/*\u0026lt;/url-pattern\u0026gt;\\u0026lt;/servlet-mapping\u0026gt;\\\
步骤二:使用Spring和JAX-RS注解声明根JAX-RS资源类:
\\@Path(\"/accounts/\")\@Component\@Scope(\"prototype\")\public class AccountResource {\\ @Context\ UriInfo uriInfo;\\ @Autowired\ private AccountRepository accountRepository;\\}\\
如下是对这些注解的说明:
\@Component将AccountResource声明为Spring bean。
\@Scope声明了一个prototype Spring bean,这样每次使用时都会实例化(比如每次请求时)。
\@Autowired指定了一个AccountRepository引用,Spring会提供该引用。
\@Path是个JAX-RS注解,它将AccountResource声明为“根”JAX-RS资源。
\@Context也是一个JAX-RS注解,要求注入特定于请求的UriInfo对象。
\JAX-RS有“根”资源(标记为@Path)和子资源的概念。在上面的示例中,AccountResource就是个根资源,它会处理以“/accounts/”开头的路径。AccountResource中的方法如getAccount()只需声明针对类型级别的相对路径即可。
\\@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;\ }\\}\\
JAX-RS中的根与子资源创建了一个处理链,它会调用多个资源:
\请记住,资源类是Web Services层组件,应当关注于Web Services相关的处理,比如输入转换、准备响应、设定响应代码等等。此外,将Web Services逻辑与业务逻辑分隔开来的实践需要将业务逻辑包装到单独的方法中以作为事务边界。
\创建Spring MVC @Controller类
\对于Spring MVC来说,我们需要创建DispatcherServlet,同时将contextConfigLocation参数指定为Spring MVC配置:
\\\u0026lt;servlet\u0026gt;\ \u0026lt;servlet-name\u0026gt;Spring MVC Dispatcher Servlet\u0026lt;/servlet-name\u0026gt;\ \u0026lt;servlet-class\u0026gt;org.springframework.web.servlet.DispatcherServlet\u0026lt;/servlet-class\u0026gt;\ \u0026lt;init-param\u0026gt;\ \u0026lt;param-name\u0026gt;contextConfigLocation\u0026lt;/param-name\u0026gt;\ \u0026lt;param-value\u0026gt;\ /WEB-INF/spring/*.xml\ \u0026lt;/param-value\u0026gt;\ \u0026lt;/init-param\u0026gt;\\u0026lt;/servlet\u0026gt;\\
要想在Spring MVC(@MVC)中使用基于注解的编程模型还需要少量的配置。下面的component-scan元素会告诉Spring去哪里寻找@Controller注解类。
\\\u0026lt;context:component-scan base-package=\"org.springframework.samples.stocks\" /\u0026gt;\
接下来,我们声明了AccountController,如下代码所示:
\\@Controller\@RequestMapping(\"/accounts\")\public class AccountController {\\ @Autowired\ private AccountRepository accountRepository;\\}\\
@RequestMapping注解会将该控制器映射到所有以“/accounts”开头的请求上。AccountController中的方法如getAccount()只需声明针对“/accounts”的相对地址即可。
\\@RequestMapping(value = \"/{username}\