JAX-RS:CXF的实现与集成

 

依赖
本文基于cxf2.7.0,需要在前面的例子中加入对jaxrs的依赖:

<dependency>  
    <groupId>org.apache.cxf</groupId>  
    <artifactId>cxf-rt-frontend-jaxrs</artifactId>  
    <version>2.7.0</version>  
</dependency>  

 由于2.7.0是采用jax-rs2.0版本,即JSR339,默认会引入:

<dependency>  
    <groupId>javax.ws.rs</groupId>  
    <artifactId>javax.ws.rs-api</artifactId>  
    <version>2.0-m10</version>  
</dependency>  

 当然对于之前的版本,基于jsr311。需要在pom中手动加入:1.0或1.1版本

 

<dependency>   
    <groupId>javax.ws.rs</groupId>   
    <artifactId>jsr311-api</artifactId>   
    <version>1.1.1</version>   
</dependency>  

 整合
这里主要考虑一下几种整合方式,更多见这里

 

  • 编程式发布server

在CXF中与JAX-WS一样,提供了JAXRSServerFactoryBean作为工厂服务类来编程式发布资源类

JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();  
//Bind one or more resources  
sf.setResourceClasses(CustomerService.class, AnotherService.class);  
// JAX-RS默认是每个请求会实例,这样修改为单例  
sf.setResourceProvider(CustomerService.class, new SingletonResourceProvider(new CustomerService()));  
sf.setAddress("http://localhost:9000/");  
BindingFactoryManager manager = sf.getBus().getExtension(BindingFactoryManager.class);  
JAXRSBindingFactory factory = new JAXRSBindingFactory();  
factory.setBus(sf.getBus());  
manager.registerBindingFactory(JAXRSBindingFactory.JAXRS_BINDING_ID, factory);  
sf.create();  
  •  spring

当然这里说的与spring整合少不了与web容器的整合,首先需要在web.xml中添加CXF的servlet监听请求路径:

<servlet>  
    <servlet-name>CXFServlet</servlet-name>  
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>  
    <load-on-startup>5</load-on-startup>  
</servlet>  
<servlet-mapping>  
    <servlet-name>CXFServlet</servlet-name>  
    <url-pattern>/*</url-pattern>  
</servlet-mapping>  

 下面介绍两种spring的配置,这也是通用的

  • jaxrs命名空间

这是一个最简单的配置:

<?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.xsd  
    http://cxf.apache.org/jaxrs  
    http://cxf.apache.org/schemas/jaxrs.xsd">  
  
    <import resource="classpath:META-INF/cxf/cxf.xml" />  
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />  
  
    <jaxrs:server id="customerService" address="/jaxrs">  
        <jaxrs:serviceBeans>  
            <ref bean="restPathService1"/>  
        </jaxrs:serviceBeans>  
  
    </jaxrs:server>  
  
    <bean id="restPathService1" class="org.ws.server.cxf.chap3.cxf.server.CustomerService"/>  
</beans>  

 当然在jaxrs:server中可以加入其他节点,具体可参考http://cxf.apache.org/schemas/jaxrs.xsd

 

  • spring bean

也可以使用普通的bean配置,只是需要作为JAXRSServerFactoryBean实例的属性:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="  
    http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/beans/spring-beans.xsd">  
  
    <import resource="classpath:META-INF/cxf/cxf.xml" />  
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />  
  
    <bean class="org.apache.cxf.jaxrs.JAXRSServerFactoryBean" init-method="create">  
        <property name="address" value="/jaxrs"/>  
        <property:serviceBeans>  
            <ref bean="restPathService1" />  
        </property:serviceBeans>  
    </bean>  
  
    <bean id="restPathService2" class="org.ws.server.cxf.chap3.cxf.server.AnotherService"/>  
</beans>  

 
数据绑定(Data Bindings)
CXF对JAX-RS的数据绑定与之前的一样,需要选择相应的绑定类型,如需要加入JAXB对XML的处理:

<dependency>  
        <groupId>org.apache.cxf</groupId>  
        <artifactId>cxf-rt-databinding-jaxb</artifactId>  
        <version>${cxf.version}</version>  
</dependency>  

 这也是CXF对JAX-RS的默认编码类型,因此我们在采用XML作为传输时只需要做简单的处理:
资源类中的一个方法:

@GET  
@Path("/meth1")  
@Produces({ MediaType.APPLICATION_XML })  
public User meth1() {  
    return new User(1, "robin", "123");  
}  

 其中User作为返回需要以XML的格式传输,最简单的处理只需要在: 

@XmlRootElement(name = "user")  
public class User {  
  
    private Integer id;  
    private String  username;  
    private String  password;   

 当然更多操作需要了解

 

再介绍对JSON格式的处理,由于CXF默认支持是依赖Jettison通常我们更喜欢Jackson。所以,首先需要加入其依赖:

<dependency>  
  <groupId>org.codehaus.jackson</groupId>  
  <artifactId>jackson-jaxrs</artifactId>  
  <version>1.9.0</version>  
</dependency>  

 需要更换默认的JSON providers为Jackson:

<jaxrs:providers>  
   <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/>  
</jaxrs:providers>  

 

<jaxrs:providers>  
   <bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>  
</jaxrs:providers>

 一个完整的例子:
资源类的方法声明:

@GET  
@Path("/meth2")  
@Produces({ MediaType.APPLICATION_JSON })  
public User meth2() {  
    return new User(1, "robin", "123");  
}  

 spring配置文件:

<jaxrs:server id="customerService" address="/jaxrs">
    <jaxrs:serviceBeans>
        <ref bean="restPathService3"/>
    </jaxrs:serviceBeans>

    <jaxrs:providers>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/>
    </jaxrs:providers>
</jaxrs:server>

<bean id="restPathService3" class="org.ws.server.cxf.chap3.cxf.data.RESTDataBindService"/>

  更多信息见这里


URI路径选择算法
当一个请求URI匹配了多个resource类,进行下面的算法:
1、优先匹配resource类@Path中更多的文字

@Path("/bar/{id}")  
public class Test1 {}  
@Path("/bar/{id}/baz")  
public class Test2 {}  
@Path("/foo")  
public class Test3 {}  
@Path("/foo/")  
public class Test4 {}  

 对请求URI: /bar/1/baz 同时匹配上了Test1和Test2,但Test2中有9个文字而Test1只有5个,同样对请求/foo/将匹配Test4

 

2、优先匹配resource类@Path中更多组类

@Path("/bar/{id}/")  
public class Test1 {}  
@Path("/bar/{id}/{id2}")  
public class Test2 {}  

 对请求URI:/bar/1/2同时匹配Test1和Test2,但Test2中有3个分组将优先匹配

 

3、优先匹配resource类@Path中捕获更多的正则组

@Path("/bar/{id:.+}/baz/{id2}")  
public class Test1 {}  
@Path("/bar/{id}/bar/{id2}")  
public class Test2 {}  

 对请求URI:/bar/1/baz/2同时匹配Test1和Test2,且文字长度和组都一样,但Test1中匹配为1而Test2匹配中0

 

在多个resource方法中选择
根据上面的规则,我们已经选择了resource类。接下来对类中方法的选择也遵循这个原则,其中只有一点额外需要注意:

如果同时包含sub-resource优先选择非sub-resource

@Path("/rest")  
public class Test1 {  
  
 @Path("/bar")  
 @GET  
 public Order getOrder() {...}  
  
 @Path("/bar")  
 public Order getOrderFromSubresource() {...}  
}  
  
public class Order {  
  
 @Path("/")  
 @GET  
 public Order getOrder() { return this; }  
  
}  

 对请求URI: /rest/bar将匹配方法getOrder()因为另外一个是sub-resource

 

 

方法与MediaType的选择
考虑这样的情况如果方法都匹配,但是MediaType不一样:

@Path("/rest")  
public class Test1 {  
  
     @Path("/bar")  
     @POST   
     @Consumes("application/json")  
     @Produces("application/json")  
     public Order addOrderJSON(OrderDetails details) {...}  
  
     @Path("/bar")  
     @POST  
     @Consumes("application/xml")  
     @Produces("application/xml")  
     public Order getOrderXML(OrderDetails details) {...}  
  
}  

 对请求URI: /rest/bar以上两个都匹配,当然如果请求的Content-Type和Accept都有设置相应的类型,那么就好判断,如application/json指向addOrderJSON,如果是application/xml则指向方法getOrderXML当然,如果客户端并没明显指定而是直接在浏览器请求,可能会选择的是getOrderXML因为默认的可能是:

Content-Type:   text/html  
Accept:         text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  

 当然其实可以更好的方式处理这种情况:

@Path("/")  
public class Test1 {  
  
 @Path("/bar")  
 @POST   
 @Consumes({"application/json", "application/xml"})  
 @Produces({"application/json", "application/xml"})  
 public Order addOrder(OrderDetails details) {...}  
  
} 

 
 自定义URI选择算法
CXF 2.2.5以后提供了自定义的URI选择器,需要实现ResourceComparator接口

public class QueryResourceInfoComperator extends OperationResourceInfoComparator implements ResourceComparator {  
  
    public QueryResourceInfoComperator() {  
        super(null, null);  
    }  
  
    @Override  
    public int compare(ClassResourceInfo cri1, ClassResourceInfo cri2, Message message) {  
        return 0;  
    }  
  
    @Override  
    public int compare(OperationResourceInfo oper1, OperationResourceInfo oper2, Message message) {  
  
        int cxfResult = super.compare(oper1, oper2);  
        if (cxfResult != 0)  
            return cxfResult;  
        //OperationResourceInfo获取resource方法请求路径相关信息  
        int op1Counter = getMatchingRate(oper1, message);  
        int op2Counter = getMatchingRate(oper2, message);  
        //通过两两比较权重选择具体的方法  
        return op1Counter == op2Counter ? 0 : op1Counter < op2Counter ? 1 : -1;  
    }  
  
    protected int getMatchingRate(OperationResourceInfo operation, Message message) {  
  
        List<Parameter> params = operation.getParameters();  
        if (params == null || params.size() == 0)  
            return 0;  
  
        Set<String> qParams = getParams((String) message.get(Message.QUERY_STRING));  
  
        //匹配更多的参数获得更多的权限  
        int rate = 0;  
        for (Parameter p : params) {  
            switch (p.getType()) {  
                case QUERY:  
                    if (qParams.contains(p.getName()))  
                        rate += 2;  
                    else if (p.getDefaultValue() == null)  
                        rate -= 1;  
                    break;  
                default:  
                    break;  
            }  
        }  
        return rate;  
    }  
  
    /** 
     * @param query URL Query Example: 'key=value&key2=value2' 
     * @return A Set of all keys, contained within query. 
     */  
    protected Set<String> getParams(String query) {  
        Set<String> params = new HashSet<String>();  
        if (query == null || query.length() == 0)  
            return params;  
  
        MultivaluedMap<String, String> allQueries = JAXRSUtils.getStructuredParams(query, "&", false, false);  
        return allQueries.keySet();  
    }  
}  

 注册相应的服务:

<jaxrs:server id="customerService" address="/jaxrs">
    <jaxrs:serviceBeans>
        <ref bean="restPathService6"/>
    </jaxrs:serviceBeans>

    <jaxrs:resourceComparator>
        <bean class="org.ws.server.cxf.chap3.cxf.uri.QueryResourceInfoComperator"/>
    </jaxrs:resourceComparator>
</jaxrs:server>

<bean id="restPathService6" class="org.ws.server.cxf.chap3.cxf.uri.CustomURITemplateService"/>  

 其中resource类的声明:

@Path("/rest")  
public class CustomURITemplateService {  
  
    @GET  
    @Path("/meth")  
    public Response meth1(@QueryParam("name") String name) {  
        return Response.status(200).entity("/rest/meth1 called[name=" + name + "]").build();  
    }  
  
    @GET  
    @Path("/meth")  
    public Response meth2(@QueryParam("name") String name, @QueryParam("age") Integer age) {  
        return Response.status(200).entity("/rest/meth2 called[name=" + name + ", age=" + age + "]").build();  
    }  
  
    @GET  
    @Path("/meth")  
    public Response meth3(@QueryParam("name") String name, @QueryParam("age") Integer age,  
                          @QueryParam("password") String password) {  
        return Response.status(200)  
                .entity("/rest/meth3 called[name=" + name + ", age=" + age + ",password=" + password + "]").build();  
    }  
  
}  

 对请求遵循以下规则:

/jaxrs/rest/meth?name=abd&age=123&password=aaa   --> meth3  
/jaxrs/rest/meth?name=abd&age=123                --> meth2  
/jaxrs/rest/meth?name=abd                        --> meth1  

 更多信息见这里

你可能感兴趣的:(CXF,Restful,jaxrs)