依赖
本文基于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
更多信息见这里