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

被折叠的 条评论
为什么被折叠?



