用CXF構建RESTful services有兩種方式:
·CXF對JAX-RS的實現。
·使用JAX-WS Provider/Dispatch API。
官網上還有Http Bindings方式,他須要作一些繁瑣的工做去建立資源再映射到服務上,這種方式從2.6時已經被移除了。
恰好我這裏有幾個工程都是用第一種方式實現的,在這裏便主要記錄一下spring+CXF構建RESTful service。java
首先列舉一下JAX-RS的一些經常使用註解。
·@Path:指定資源的URI。
·@Produces/@Consumes:指定請求/響應的媒體類型。當類和方法同時被標註時,方法標註會覆蓋類標註。
·@GET,@POST,@PUT,@DELETE,@HEAD,@OPTIONS:指定請求的Http method。
·@QueryParam,@PathParam,@HeaderParam,@FormParam,@CookieParam:指定參數值的來源,可標註於類、方法、屬性。
符合如下規則的參數值能夠被接收:
·原始類型
·擁有一個String參數的constructor
·有valueOf或者fromString靜態method
另外部分來源也支持SortedSet<T>、List<T>和Set<T>,T須要知足上面的規則。
web
相關dependency:
spring
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-bundle-jaxrs</artifactId> <version>${cxf.version}</version> </dependency>
如今我打算定義一個服務用來返回一組用戶信息。
apache
當以get method訪問users資源時將以XML表述:
瀏覽器
package pac.king.webservice; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import pac.king.pojo.User; @Path("/") public interface MyRestService { @GET @Path("users") @Produces({ MediaType.APPLICATION_XML }) public User[] userInfos(); }
接口實現我就簡單寫一下:
多線程
public class MyRestServiceImpl implements MyRestService{ @WebMethod public User[] userInfos() { User[] myInfos = new User[4]; myInfos[0] = new User("0001","Kim","t;stmdtkg"); myInfos[1] = new User("0002","King.","t;stmdtkg"); myInfos[2] = new User("0003","sweet_dreams","t;stmdtkg"); myInfos[3] = new User("0004","show_time","t;stmdtkg"); return myInfos; } }
定義User時須要注意加上無參的constructor和@XmlRootElement
app
package pac.king.pojo; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class User { private String id; private String name; private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User() {} public User(String id, String name, String password) { super(); this.id = id; this.name = name; this.password = password; } }
使用org.apache.cxf.jaxrs.JAXRSServerFactoryBean啓動服務:
dom
JAXRSServerFactoryBean rsFactory = new JAXRSServerFactoryBean(); rsFactory.setAddress("http://localhost:8888/myRest"); rsFactory.setResourceClasses(MyRestServiceImpl.class); rsFactory.create();
訪問http://localhost:8888/myRest/users,輸出:
ide
用CXF+Spring方式構建RESTful service也很是方便,雖然也會帶來一些問題。
服務就繼續用上面定義的MyRestService,可是Service的部分屬性將定義在XML configration中,並將service放到容器裏。
(其實也能夠non-Spring配置到容器裏,很難想象爲何要用這種方式,但彷佛能夠明白點點什麼。)
spring配置,引入了一些沒必要要的namespace,但也沒什麼大問題:
this
<?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" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:cxf="http://cxf.apache.org/core" 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 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd " default-autowire="byName"> <import resource="classpath:META-INF/cxf/cxf.xml"/> <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/> <import resource="classpath:META-INF/cxf/osgi/cxf-extension-osgi.xml" /> <jaxrs:server id="customerService" address="/rest" > <jaxrs:serviceBeans> <ref bean="myRestService"/> </jaxrs:serviceBeans> </jaxrs:server> <bean id="myRestService" class="pac.king.webservice.impl.MyRestServiceImpl"/> </beans>
在web.xml中加入CXFServlet,注意我寫的url pattern是/services/*:
<servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> <servlet>
訪問http://localhost:8080/runtrain/services,效果以下(還有一個是上一篇的JAX-WS服務):
訪問http://localhost:8080/runtrain/services/rest/users,效果以下:
最後說一下lifecycle的問題。
像這個例子中用bean標籤訂義一個服務,此時給他加上scope標籤不會有任何效果。
他始終是默認的——singleton。
jaxrs:server下還有一個子標籤叫jaxrs:serviceFactories,裏面能夠存放org.apache.cxf.jaxrs.spring.SpringResourceFactory類型的Bean,SpringResourceFactory將會把服務的聲明週期委派給ApplicationContext來管理。
我能夠作以下配置:
<bean id="myRestService" class="pac.king.webservice.impl.MyRestServiceImpl" scope="prototype"/> <jaxrs:server id="customerService" address="/rest" > <jaxrs:serviceFactories> <ref bean="resourceFactory" /> </jaxrs:serviceFactories> </jaxrs:server> <bean id="resourceFactory" class="org.apache.cxf.jaxrs.spring.SpringResourceFactory"> <property name="beanId" value="myRestService" /> </bean>
但這種方式有些麻煩,難道我就爲了讓scope生效定義bean又定義SpringResourceFactory又設置serviceFactoris?
能夠更簡便地配置,以下:
<beans> <jaxrs:server id="customerService" address="/rest" beanNames="myRestService" /> <bean id="myRestService" class="pac.king.webservice.impl.MyRestServiceImpl" scope="prototype"/> </beans>
須要注意的是beanNames屬性中寫多個值時以space分隔。
別人辛苦構建了RESTful Service,總不能只用瀏覽器調用。
繼續說說Client端的API。
繼續使用上面的例子,此次加個參數,以下:
package pac.king.webservice; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import pac.king.pojo.User; @Path("/") public interface MyRestService { @GET @Path("limitUsers/{count}") @Produces({ MediaType.APPLICATION_XML }) public User[] userInfos(@PathParam("count")int cnt); }
實現:
public User[] userInfos(int count) { System.out.println("count="+count); User[] myInfos = new User[count]; for (int i = 0; i < count; i++) { myInfos[i] = new User(i+1+"","King."+UUID.randomUUID(),"t;stmdtkg"); } return myInfos; }
User類:
package pac.king.pojo; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class User { private String id; private String name; private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User() {} public User(String id, String name, String password) { super(); this.id = id; this.name = name; this.password = password; } }
固然,某種程度上org.apache.commons.httpclient.HttpClient也能夠調用。
但稍有複雜的狀況就沒法勝任。
基於代理的API主要是(和spring裏的那幾個ProxyFactoryBean不一樣,名字裏不帶Proxy)
·org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean
好比我能夠這樣使用:
JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean(); bean.setAddress("http://localhost:8080/runtrain/services/rest"); bean.setResourceClass(MyRestServiceImpl.class); MyRestServiceImpl proxy = (MyRestServiceImpl)bean.create();
另外,JAXRSClientFactoryBean
有一個工廠類:
·org.apache.cxf.jaxrs.client.JAXRSClientFactory
好比能夠這樣使用:
MyRestServiceImpl client = JAXRSClientFactory.create("http://localhost:8080/runtrain/services/rest", MyRestServiceImpl.class); User[] users = client.userInfos(10);
讓人感受很奇怪,提供了一個工廠類卻能夠直接使用Bean,官網上沒找到答案,我也不糾結了吧。
上面代碼中使用的MyRestServiceImpl和Server端能夠是沒有任何關係的,讓遠程調用變得透明也正是proxy的意義。
值得注意的是,有一個threadSafe屬性,同一個代理是否容許被多線程訪問取決於此。
除了JAXRSClientFactoryBean,還有Http-centric web client:
·org.apache.cxf.jaxrs.client.WebClient
舉個例子:
WebClient webClient = WebClient.create("http://localhost:8080/runtrain/services/rest"); webClient .path("limitUsers").path(new Integer(10)); webClient .type(MediaType.APPLICATION_XML).accept(MediaType.APPLICATION_XML); User[] res = webClient.get(User[].class); System.out.println(res.length);