如今您已經學會了如何使用 CXF 開發基於 SOAP 的 Web 服務,也領略了 Spring + CXF 這個強大的組合,若是您錯過了這精彩的一幕,請回頭看看這篇吧:css
Web Service 那點事兒(2) —— 使用 CXF 開發 SOAP 服務html
今天咱們將視角集中在 REST 上,它是繼 SOAP 之後,另外一種普遍使用的 Web 服務。與 SOAP 不一樣,REST 並無 WSDL 的概念,也沒有叫作「信封」的東西,由於 REST 主張用一種簡單粗暴的方式來表達數據,傳遞的數據格式能夠是 JSON 格式,也能夠是 XML 格式,這徹底由您來決定。前端
REST 全稱是 Representational State Transfer(表述性狀態轉移),它是 Roy Fielding 博士在 2000 年寫的一篇關於軟件架構風格的論文,此文一出,震撼四方!許多知名互聯網公司開始採用這種輕量級 Web 服務,你們習慣將其稱爲 RESTful Web Services
,或簡稱 REST 服務
。java
那麼 REST 究竟是什麼呢?jquery
REST 本質上是使用 URL 來訪問資源的一種方式。總所周知,URL 就是咱們日常使用的請求地址了,其中包括兩部分:請求方式
與 請求路徑
,比較常見的請求方式是 GET 與 POST,但在 REST 中又提出了其它幾種其它類型的請求方式,彙總起來有六種:GET、POST、PUT、DELETE、HEAD、OPTIONS。尤爲是前四種,正好與 CRUD(增刪改查)四種操做相對應:GET(查)、POST(增)、PUT(改)、DELETE(刪),這正是 REST 的奧妙所在!git
實際上,REST 是一個「無狀態」的架構模式,由於在任什麼時候候均可以由客戶端發出請求到服務端,最終返回本身想要的數據。也就是說,服務端將內部資源發佈 REST 服務,客戶端經過 URL 來訪問這些資源,這不就是 SOA 所提倡的「面向服務」的思想嗎?因此,REST 也被人們看作是一種輕量級的 SOA 實現技術,所以在企業級應用與互聯網應用中都獲得了普遍使用。web
在 Java 的世界裏,有一個名爲 JAX-RS
的規範,它就是用來實現 REST 服務的,目前已經發展到了 2.0 版本,也就是 JSR-339 規範,若是您想深刻研究 REST,請深刻閱讀此規範。ajax
JAX-RS 規範目前有如下幾種比較流行的實現技術:spring
本文以 CXF 爲例,我努力用最精煉的文字,讓您快速學會如何使用 CXF 開發 REST 服務,此外還會將 Spring 與 CXF 作一個整合,讓開發更加高效!express
那麼還等什麼呢?我們一塊兒出發吧!
第一步:添加 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); }
以上 ProductService
接口中提供了一系列的方法,在每一個方法上都使用了 JAX-RS 提供的註解,主要包括如下三類:
針對 updateProductById
方法,簡單解釋一下:
該方法將被 PUT:/product/{id}
請求來調用,請求路徑中的 id
參數將映射到 long id
參數上,請求體中的數據將自動轉換爲 JSON 格式並映射到 Map<String, Object> fieldMap
參數上,返回的 Product
類型的數據將自動轉換爲 JSON 格式並返回到客戶端。
注意:因爲 Product
類與 ProductService
接口的實現類並非本文的重點,所以省略了,本文結尾處會給出源碼連接。
第三步:使用 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 提供了一個名爲 org.apache.cxf.jaxrs.JAXRSServerFactoryBean
的類,專用於發佈 REST 服務,只需爲該類的實例對象指定四個屬性便可:
org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider
進行裝飾org.codehaus.jackson.jaxrs.JacksonJsonProvider
,用於實現 JSON 數據的序列化與反序列化運行以上 Server
類,將以 standalone 方式發佈 REST 服務,下面咱們經過客戶端來調用以發佈的 REST 服務。
第四步:使用 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 提供的 org.apache.cxf.jaxrs.client.JAXRSClientFactory
工廠類來建立 ProductService
代理對象,經過代理對象調用目標對象上的方法。客戶端一樣也須要使用 Provider,此時仍然使用了 Jackson 提供的 org.codehaus.jackson.jaxrs.JacksonJsonProvider
。
第二種: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 中提供了一個名爲 javax.ws.rs.client.ClientBuilder
的工具類,可用於建立客戶端並調用 REST 服務,顯然這種方式比前一種要先進,由於在代碼中再也不依賴 CXF API 了。
若是想返回帶有泛型的 List<Product>
,那麼可使用如下代碼片斷:
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 還提供了一種更爲簡潔的方式,使用 org.apache.cxf.jaxrs.client.WebClient
來調用 REST 服務,這種方式在代碼層面上仍是至關簡潔的。
若是想返回帶有泛型的 List<Product>
,那麼可使用如下代碼片斷:
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); }
第一步:添加 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 提供的 ContextLoaderListener
去加載 Spring 配置文件 spring.xml;使用 CXF 提供的 CXFServlet
去處理前綴爲 /ws/
的 REST 請求。
第三步:將接口的實現類發佈 SpringBean
package demo.ws.rest_spring_cxf;
import org.springframework.stereotype.Component;
@Component
public class ProductServiceImpl implements ProductService {
... }
使用 Spring 提供的 @Component
註解,將 ProductServiceImpl
發佈爲 Spring Bean,交給 Spring IOC 容器管理,無需再進行 Spring XML 配置。
第四步:配置 Spring
如下是 spring.xml
的配置:
<?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>
在以上配置中掃描 demo.ws
這個基礎包路徑,Spring 可訪問該包中的全部 Spring Bean,好比,上面使用 @Component
註解發佈的 ProductServiceImpl
。此外,加載了另外一個配置文件 spring-cxf.xml,其中包括了關於 CXF 的相關配置。
如下是 spring-cxf.xml
的配置:
<?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 等技術。
若是服務端部署在 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,"name":"ipad mini","price":2500},{"id":1,"name":"iphone 5s","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>
注意:JsonpPreStreamInterceptor
必定要放在 <jaxrs:providers>
中,而不是 <jaxrs:inInterceptors>
中,這也許是 CXF 的一個 Bug,能夠點擊如下連接查看具體緣由:
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); } });
以上代碼中有三個選項須要加以說明:
JsonpInInterceptor
中配置方案二:使用 CORS 解決 AJAX 跨域問題
CORS 的全稱是 Cross-Origin Resource Sharing(跨域資源共享),它是 W3C 提出的一個 AJAX 跨域訪問規範,能夠從如下地址瞭解此規範:
相比 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>
在 CrossOriginResourceSharingFilter
中配置 allowOrigins
屬性,將其設置爲客戶端的域名,示例中爲「http://localhost」,需根據實際狀況進行設置。
最後,使用 jQuery 發送 AJAX 請求:
就像在相同域名下訪問同樣,無需作任何配置。
注意:在 IE8 中使用 jQuery 發送 AJAX 請求時,須要配置 $.support.cors = true
,才能開啓 CORS 特性。
本文讓您學會了如何使用 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 安全咱們應該如何保證呢?下一篇將爲您揭曉,敬請期待!