webService——学习(5):使用 CXF 开发 REST 服务
现在您已经学会了如何使用 CXF 开发基于 SOAP 的 Web 服务,也领略了 Spring + CXF 这个强大的组合,如果您错过了这精彩的一幕,请回头看看这篇吧: Web Service 那点事儿(2) —— 使用 CXF 开发 SOAP 服务 今天我们将视角集中在 REST 上,它是继 SOAP 以后,另一种广泛使用的 Web 服务。与 SOAP 不同,REST 并没有 WSDL 的概念,也没有叫做“信封”的东西,因为 REST 主张用一种简单粗暴的方式来表达数据,传递的数据格式可以是 JSON 格式,也可以是 XML 格式,这完全由您来决定。 REST 全称是 Representational State Transfer(表述性状态转移),它是 Roy Fielding 博士在 2000 年写的一篇关于软件架构风格的论文,此文一出,震撼四方!许多知名互联网公司开始采用这种轻量级 Web 服务,大家习惯将其称为? 那么 REST 到底是什么呢? REST 本质上是使用 URL 来访问资源的一种方式。总所周知,URL 就是我们平常使用的请求地址了,其中包括两部分: 实际上,REST 是一个“无状态”的架构模式,因为在任何时候都可以由客户端发出请求到服务端,最终返回自己想要的数据。也就是说,服务端将内部资源发布 REST 服务,客户端通过 URL 来访问这些资源,这不就是 SOA 所提倡的“面向服务”的思想吗?所以,REST 也被人们看做是一种轻量级的 SOA 实现技术,因此在企业级应用与互联网应用中都得到了广泛使用。 在 Java 的世界里,有一个名为? JAX-RS 规范目前有以下几种比较流行的实现技术:
本文以 CXF 为例,我努力用最精炼的文字,让您快速学会如何使用 CXF 开发 REST 服务,此外还会将 Spring 与 CXF 做一个整合,让开发更加高效! 那么还等什么呢?咱们一起出发吧!
1. 使用 CXF 发布与调用 REST 服务第一步:添加 Maven 依赖 <?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>demo.ws</groupId> ????<artifactId>rest_cxf</artifactId> ????<version>1.0-SNAPSHOT</version> ????<properties> ????????<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> ????????<cxf.version>3.0.0</cxf.version> ????????<jackson.version>2.4.1</jackson.version> ????</properties> ????<dependencies> ????????<!--?CXF?--> ????????<dependency> ????????????<groupId>org.apache.cxf</groupId> ????????????<artifactId>cxf-rt-frontend-jaxrs</artifactId> ????????????<version>${cxf.version}</version> ????????</dependency> ????????<dependency> ????????????<groupId>org.apache.cxf</groupId> ????????????<artifactId>cxf-rt-transports-http-jetty</artifactId> ????????????<version>${cxf.version}</version> ????????</dependency> ????????<!--?Jackson?--> ????????<dependency> ????????????<groupId>com.fasterxml.jackson.jaxrs</groupId> ????????????<artifactId>jackson-jaxrs-json-provider</artifactId> ????????????<version>${jackson.version}</version> ????????</dependency> ????</dependencies></project> 以上添加了 CXF 关于 REST 的依赖包,并使用了 Jackson 来实现 JSON 数据的转换。 第二步:定义一个 REST 服务接口 package?demo.ws.rest_cxf;import?java.util.List;import?java.util.Map;import?javax.ws.rs.Consumes;import?javax.ws.rs.DELETE;import?javax.ws.rs.FormParam;import?javax.ws.rs.GET;import?javax.ws.rs.POST;import?javax.ws.rs.PUT;import?javax.ws.rs.Path;import?javax.ws.rs.PathParam;import?javax.ws.rs.Produces;import?javax.ws.rs.core.MediaType; public?interface?ProductService?{????@GET ????@Path("/products")????@Produces(MediaType.APPLICATION_JSON)????List<Product>?retrieveAllProducts();????@GET ????@Path("/product/{id}")????@Produces(MediaType.APPLICATION_JSON) ????Product?retrieveProductById(@PathParam("id")?long?id);????@POST ????@Path("/products")????@Consumes(MediaType.APPLICATION_FORM_URLENCODED)????@Produces(MediaType.APPLICATION_JSON)????List<Product>?retrieveProductsByName(@FormParam("name")?String?name);????@POST ????@Path("/product")????@Consumes(MediaType.APPLICATION_JSON)????@Produces(MediaType.APPLICATION_JSON) ????Product?createProduct(Product?product);????@PUT ????@Path("/product/{id}")????@Consumes(MediaType.APPLICATION_JSON)????@Produces(MediaType.APPLICATION_JSON) ????Product?updateProductById(@PathParam("id")?long?id,?Map<String,?Object>?fieldMap);????@DELETE ????@Path("/product/{id}")????@Produces(MediaType.APPLICATION_JSON) ????Product?deleteProductById(@PathParam("id")?long?id); } 以上?
针对? 该方法将被? 注意:由于? 第三步:使用 CXF 发布 REST 服务 package?demo.ws.rest_cxf;import?java.util.ArrayList;import?java.util.List;import?org.apache.cxf.jaxrs.JAXRSServerFactoryBean;import?org.apache.cxf.jaxrs.lifecycle.ResourceProvider;import?org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;import?org.codehaus.jackson.jaxrs.JacksonJsonProvider; public?class?Server?{ ????public?static?void?main(String[]?args)?{????????//?添加?ResourceClass ????????List<Class<?>>?resourceClassList?=?new?ArrayList<Class<?>>(); ????????resourceClassList.add(ProductServiceImpl.class);????????//?添加?ResourceProvider ????????List<ResourceProvider>?resourceProviderList?=?new?ArrayList<ResourceProvider>(); ????????resourceProviderList.add(new?SingletonResourceProvider(new?ProductServiceImpl()));????????//?添加?Provider ????????List<Object>?providerList?=?new?ArrayList<Object>(); ????????providerList.add(new?JacksonJsonProvider());????????//?发布?REST?服务 ????????JAXRSServerFactoryBean?factory?=?new?JAXRSServerFactoryBean();????????factory.setAddress("http://localhost:8080/ws/rest");????????factory.setResourceClasses(resourceClassList);????????factory.setResourceProviders(resourceProviderList);????????factory.setProviders(providerList);????????factory.create(); ????????System.out.println("rest?ws?is?published"); ????} } CXF 提供了一个名为?
运行以上? 第四步:使用 CXF 调用 REST 服务 首先添加如下 Maven 依赖: <dependency> ????<groupId>org.apache.cxf</groupId> ????<artifactId>cxf-rt-rs-client</artifactId> ????<version>${cxf.version}</version></dependency> CXF 提供了三种 REST 客户端,下面将分别进行展示。 第一种:JAX-RS 1.0 时代的客户端 package?demo.ws.rest_cxf;import?java.util.ArrayList;import?java.util.List;import?org.apache.cxf.jaxrs.client.JAXRSClientFactory;import?org.codehaus.jackson.jaxrs.JacksonJsonProvider; public?class?JAXRSClient?{ ????public?static?void?main(String[]?args)?{????????String?baseAddress?=?"http://localhost:8080/ws/rest";????????List<Object>?providerList?=?new?ArrayList<Object>(); ????????providerList.add(new?JacksonJsonProvider()); ????????ProductService?productService?=?JAXRSClientFactory.create(baseAddress,?ProductService.class,?providerList);????????List<Product>?productList?=?productService.retrieveAllProducts();????????for?(Product?product?:?productList)?{ ????????????System.out.println(product); ????????} ????} } 本质是使用 CXF 提供的? 第二种:JAX-RS 2.0 时代的客户端 package?demo.ws.rest_cxf;import?java.util.List;import?javax.ws.rs.client.ClientBuilder;import?javax.ws.rs.core.MediaType;import?org.codehaus.jackson.jaxrs.JacksonJsonProvider; public?class?JAXRS20Client?{ ????public?static?void?main(String[]?args)?{????????String?baseAddress?=?"http://localhost:8080/ws/rest"; ????????JacksonJsonProvider?jsonProvider?=?new?JacksonJsonProvider();????????List?productList?=?ClientBuilder.newClient() ????????????.register(jsonProvider) ????????????.target(baseAddress) ????????????.path("/products") ????????????.request(MediaType.APPLICATION_JSON) ????????????.get(List.class);????????for?(Object?product?:?productList)?{ ????????????System.out.println(product); ????????} ????} } 在 JAX-RS 2.0 中提供了一个名为? 如果想返回带有泛型的? List<Product>?productList?=?ClientBuilder.newClient() ????.register(jsonProvider) ????.target(baseAddress) ????.path("/products") ????.request(MediaType.APPLICATION_JSON) ????.get(new?GenericType<List<Product>>()?{}); for?(Product?product?:?productList)?{ ????System.out.println(product); } 第三种:通用的 WebClient 客户端 package?demo.ws.rest_cxf;import?java.util.ArrayList;import?java.util.List;import?javax.ws.rs.core.GenericType;import?javax.ws.rs.core.MediaType;import?org.apache.cxf.jaxrs.client.WebClient;import?org.codehaus.jackson.jaxrs.JacksonJsonProvider; public?class?CXFWebClient?{ ????public?static?void?main(String[]?args)?{????????String?baseAddress?=?"http://localhost:8080/ws/rest";????????List<Object>?providerList?=?new?ArrayList<Object>(); ????????providerList.add(new?JacksonJsonProvider());????????List?productList?=?WebClient.create(baseAddress,?providerList) ????????????.path("/products") ????????????.accept(MediaType.APPLICATION_JSON) ????????????.get(List.class);????????for?(Object?product?:?productList)?{ ????????????System.out.println(product); ????????} ????} } CXF 还提供了一种更为简洁的方式,使用? 如果想返回带有泛型的? List<Product>?productList?=?WebClient.create(baseAddress,?providerList) ????.path("/products") ????.accept(MediaType.APPLICATION_JSON) ????.get(new?GenericType<List<Product>>()?{}); for?(Product?product?:?productList)?{ ????System.out.println(product); }
2. 使用 Spring + CXF 发布 REST 服务第一步:添加 Maven 依赖 <?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>demo.ws</groupId> ????<artifactId>rest_spring_cxf</artifactId> ????<version>1.0-SNAPSHOT</version> ????<packaging>war</packaging> ????<properties> ????????<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> ????????<spring.version>4.0.6.RELEASE</spring.version> ????????<cxf.version>3.0.0</cxf.version> ????????<jackson.version>2.4.1</jackson.version> ????</properties> ????<dependencies> ????????<!--?Spring?--> ????????<dependency> ????????????<groupId>org.springframework</groupId> ????????????<artifactId>spring-web</artifactId> ????????????<version>${spring.version}</version> ????????</dependency> ????????<!--?CXF?--> ????????<dependency> ????????????<groupId>org.apache.cxf</groupId> ????????????<artifactId>cxf-rt-frontend-jaxrs</artifactId> ????????????<version>${cxf.version}</version> ????????</dependency> ????????<!--?Jackson?--> ????????<dependency> ????????????<groupId>com.fasterxml.jackson.jaxrs</groupId> ????????????<artifactId>jackson-jaxrs-json-provider</artifactId> ????????????<version>${jackson.version}</version> ????????</dependency> ????</dependencies></project> 这里仅依赖 Spring Web 模块(无需 MVC 模块),此外就是 CXF 与 Jackson 了。 第二步:配置 web.xml <?xml?version="1.0"?encoding="UTF-8"?><web-app?xmlns="http://java.sun.com/xml/ns/javaee" ?????????xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ?????????xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ?????????http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" ?????????version="3.0"> ????<!--?Spring?--> ????<context-param> ????????<param-name>contextConfigLocation</param-name> ????????<param-value>classpath:spring.xml</param-value> ????</context-param> ????<listener> ????????<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> ????</listener> ????<!--?CXF?--> ????<servlet> ????????<servlet-name>cxf</servlet-name> ????????<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> ????</servlet> ????<servlet-mapping> ????????<servlet-name>cxf</servlet-name> ????????<url-pattern>/ws/*</url-pattern> ????</servlet-mapping></web-app> 使用 Spring 提供的? 第三步:将接口的实现类发布 SpringBean package?demo.ws.rest_spring_cxf; import?org.springframework.stereotype.Component; @Component public?class?ProductServiceImpl?implements?ProductService?{????...} 使用 Spring 提供的? 第四步:配置 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:context="http://www.springframework.org/schema/context" ???????xsi:schemaLocation="http://www.springframework.org/schema/beans ???????http://www.springframework.org/schema/beans/spring-beans-4.0.xsd ???????http://www.springframework.org/schema/context ???????http://www.springframework.org/schema/context/spring-context-4.0.xsd"> ????<context:component-scan?base-package="demo.ws"/> ????<import?resource="spring-cxf.xml"/></beans> 在以上配置中扫描? 以下是? <?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:jaxrs="http://cxf.apache.org/jaxrs" ???????xsi:schemaLocation="http://www.springframework.org/schema/beans ???????http://www.springframework.org/schema/beans/spring-beans-4.0.xsd ???????http://cxf.apache.org/jaxrs ???????http://cxf.apache.org/schemas/jaxrs.xsd"> ????<jaxrs:server?address="/rest"> ????????<jaxrs:serviceBeans> ????????????<ref?bean="productServiceImpl"/> ????????</jaxrs:serviceBeans> ????????<jaxrs:providers> ????????????<bean?class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/> ????????</jaxrs:providers> ????</jaxrs:server></beans> 使用 CXF 提供的 Spring 命名空间来配置 Service Bean(即上文提到的 Resource Class)与 Provider。注意,这里配置了一个 address 属性为“/rest”,表示 REST 请求的相对路径,与 web.xml 中配置的“/ws/*”结合起来,最终的 REST 请求根路径是“/ws/rest”,在 ProductService 接口方法上?@Path?注解所配置的路径只是一个相对路径。 第五步:调用 REST 服务 <!DOCTYPE?html><html><head> ????<meta?charset="UTF-8"> ????<title>Demo</title> ????<link?href="http://cdn.bootcss.com/bootstrap/3.1.1/css/bootstrap.min.css"?rel="stylesheet"></head><body><div?class="container"> ????<div?class="page-header"> ????????<h1>Product</h1> ????</div> ????<div?class="panel?panel-default"> ????????<div?class="panel-heading">Product?List</div> ????????<div?class="panel-body"> ????????????<div?id="product"></div> ????????</div> ????</div></div><script?src="http://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script><script?src="http://cdn.bootcss.com/bootstrap/3.1.1/js/bootstrap.min.js"></script><script?src="http://cdn.bootcss.com/handlebars.js/1.3.0/handlebars.min.js"></script><script?type="text/x-handlebars-template"?id="product_table_template"> ????{{#if?data}} ????????<table?class="table?table-hover"?id="product_table"> ????????????<thead> ????????????????<tr> ????????????????????<th>ID</th> ????????????????????<th>Product?Name</th> ????????????????????<th>Price</th> ????????????????</tr> ????????????</thead> ????????????<tbody> ????????????????{{#data}} ????????????????????<tr?data-id="{{id}}"?data-name="{{name}}"> ????????????????????????<td>{{id}}</td> ????????????????????????<td>{{name}}</td> ????????????????????????<td>{{price}}</td> ????????????????????</tr> ????????????????{{/data}} ????????????</tbody> ????????</table> ????{{else}} ????????<div?class="alert?alert-warning">Can?not?find?any?data!</div>????{{/if}}</script><script> ????$(function()?{ ????????$.ajax({????????????type:?'get',????????????url:?'http://localhost:8080/ws/rest/products',????????????dataType:?'json',????????????success:?function(data)?{????????????????var?template?=?$("#product_table_template").html();????????????????var?render?=?Handlebars.compile(template);????????????????var?html?=?render({ ????????????????????data:?data ????????????????}); ????????????????$('#product').html(html); ????????????} ????????}); ????});</script></body></html> 使用一个简单的 HTML 页面来调用 REST 服务,也就是说,前端发送 AJAX 请求来调用后端发布的 REST 服务。这里使用了 jQuery、Bootstrap、Handlebars.js 等技术。
3. 关于 AJAX 的跨域问题如果服务端部署在 foo.com 域名下,而客户端部署在 bar.com 域名下,此时从 bar.com 发出一个 AJAX 的 REST 请求到 foo.com,就会出现报错: No 'Access-Control-Allow-Origin' header is present on the requested resource. 要想解决以上这个 AJAX 跨域问题,有以下两种解决方案: 方案一:使用 JSONP 解决 AJAX 跨域问题 JSONP 的全称是 JSON with Padding,实际上是在需要返回的 JSON 数据外,用一个 JS 函数进行封装。 可以这样来理解,服务器返回一个 JS 函数,参数是一个 JSON 数据,例如:callback({您的 JSON 数据}),虽然 AJAX 不能跨域访问,但 JS 脚本是可以跨域执行的,因此客户端将执行这个 callback 函数,并获取其中的 JSON 数据。 如果需要返回的 JSON 数据是: {"id":2,"name":"ipad?mini","price":2500},{"id":1,"name":"iphone?5s","price":5000} 那么对应的 JSONP 格式是: callback([{"id":2,"price":5000}]); CXF 已经提供了对 JSONP 的支持,只需要通过简单的配置即可实现。 首先,添加 Maven 依赖: <dependency> ????<groupId>org.apache.cxf</groupId> ????<artifactId>cxf-rt-rs-extension-providers</artifactId> ????<version>${cxf.version}</version></dependency> 然后,添加 CXF 配置: <jaxrs:server?address="/rest"> ????<jaxrs:serviceBeans> ????????<ref?bean="productServiceImpl"/> ????</jaxrs:serviceBeans> ????<jaxrs:providers> ????????<bean?class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/> ????????<bean?class="org.apache.cxf.jaxrs.provider.jsonp.JsonpPreStreamInterceptor"/> ????</jaxrs:providers> ????<jaxrs:inInterceptors> ????????<bean?class="org.apache.cxf.jaxrs.provider.jsonp.JsonpInInterceptor"/> ????</jaxrs:inInterceptors> ????<jaxrs:outInterceptors> ????????<bean?class="org.apache.cxf.jaxrs.provider.jsonp.JsonpPostStreamInterceptor"/> ????</jaxrs:outInterceptors></jaxrs:server> 注意: http://cxf.547215.n5.nabble.com/JSONP-is-not-works-td5739858.html 最后,使用 jQuery 发送基于 JSONP 的 AJAX 请求: <!--?lang:?js?--> $.ajax({????type:?'get',????url:?'http://localhost:8080/ws/rest/products',????dataType:?'jsonp',????jsonp:?'_jsonp',????jsonpCallback:?'callback',????success:?function(data)?{????????var?template?=?$("#product_table_template").html();????????var?render?=?Handlebars.compile(template);????????var?html?=?render({ ????????????data:?data ????????}); ????????$('#product').html(html); ????} }); 以上代码中有三个选项需要加以说明:
方案二:使用 CORS 解决 AJAX 跨域问题 CORS 的全称是 Cross-Origin Resource Sharing(跨域资源共享),它是 W3C 提出的一个 AJAX 跨域访问规范,可以从以下地址了解此规范: http://www.w3.org/TR/cors/ 相比 JSONP 而言,CORS 更为强大,因为它弥补了 JSONP 只能处理 GET 请求的局限性,但是只有较为先进的浏览器才能全面支持 CORS。 CXF 同样也提供了对 CORS 的支持,通过简单的配置就能实现。 首先,添加 Maven 依赖: <dependency> ????<groupId>org.apache.cxf</groupId> ????<artifactId>cxf-rt-rs-security-cors</artifactId> ????<version>${cxf.version}</version></dependency> 然后,添加 CXF 配置: <jaxrs:server?address="/rest"> ????<jaxrs:serviceBeans> ????????<ref?bean="productServiceImpl"/> ????</jaxrs:serviceBeans> ????<jaxrs:providers> ????????<bean?class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/> ????????<bean?class="org.apache.cxf.rs.security.cors.CrossOriginResourceSharingFilter"> ????????????<property?name="allowOrigins"?value="http://localhost"/> ????????</bean> ????</jaxrs:providers></jaxrs:server> 在? 最后,使用 jQuery 发送 AJAX 请求: 就像在相同域名下访问一样,无需做任何配置。 注意:在 IE8 中使用 jQuery 发送 AJAX 请求时,需要配置?
4. 总结本文让您学会了如何使用 CXF 发布 REST 服务,可以独立使用 CXF,也可以与 Spring 集成。此外,CXF 也提供了一些解决方案,用于实现跨域 AJAX 请求,比如:JSONP 或 CORS。CXF 3.0 以全面支持 JAX-RS 2.0 规范,有很多实用的功能需要您进一步学习,可以点击以下地址: http://cxf.apache.org/docs/jax-rs.html 目前您所看到的 REST 请求没有任何的身份认证,这样是很不安全的,也就意味着任何人只要知道了 REST 地址就能调用。我们知道 SOAP 里有 WS-Security 规范,可以使用 WSS4J 来做 SOAP 安全,那么关于 REST 安全我们应该如何保证呢?下一篇将为您揭晓,敬请期待!
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |