【Apache CXF】CXF對JAX-RS的支持

用CXF構建RESTful services有兩種方式:
·CXF對JAX-RS的實現。
·使用JAX-WS Provider/Dispatch API。
官網上還有Http Bindings方式,他需要做一些繁瑣的工作去創建資源再映射到服務上,這種方式從2.6時已經被移除了。
剛好我這裏有幾個工程都是用第一種方式實現的,在這裏便主要記錄一下spring+CXF構建RESTful service。


首先列舉一下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需要滿足上面的規則。


相關dependency:

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-bundle-jaxrs</artifactId>
    <version>${cxf.version}</version>
</dependency>



現在我打算定義一個服務用來返回一組用戶信息。

當以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

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啓動服務:

JAXRSServerFactoryBean rsFactory = new JAXRSServerFactoryBean();
rsFactory.setAddress("http://localhost:8888/myRest");
rsFactory.setResourceClasses(MyRestServiceImpl.class);
rsFactory.create();



訪問http://localhost:8888/myRest/users,輸出:
wKiom1NwZpzzcbZHAAEFqQscg-c810.jpg



用CXF+Spring方式構建RESTful service也非常方便,雖然也會帶來一些問題。
服務就繼續用上面定義的MyRestService,但是Service的部分屬性將定義在XML configration中,並將service放到容器裏。
(其實也可以non-Spring配置到容器裏,很難想象爲什麼要用這種方式,但似乎可以明白點點什麼。)
spring配置,引入了一些不必要的namespace,但也沒什麼大問題:

<?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服務):

wKiom1NwquOC0yScAAGaZKy6WCs022.jpg



訪問http://localhost:8080/runtrain/services/rest/users,效果如下:
wKioL1Nwqv-RkOOFAAEIWlo3j3I022.jpg



最後說一下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);



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章