使用CXF开发RestFul WebService问题解决方案
最近在项目中,因为几个系统自己需要数据交换,所以采用进来都比较流行的RestFul风格WebService,实现框架采用apache的cxf,apache的东西一直以来都是比较的好用,回话少说,进入正题。
首先,提出两个问题,已经解决方案,后面跟上代码。
1、cxf中如何实现java中泛型的数据序列化和反序列化(通常使用json、xml格式,cxf默认不支持泛型)
2、cxf后台异常,在前台如何反映
问题1答案比较简单,cxf中的数据序列化是可以替换掉使用你实现MessageBodyReader<Object>和MessageBodyWriter<Object>接口就可以啦,针对xml,cxf采用stax2、jaxb、xmlschema、Woodstox库,针对json默认使用jettison实现的几乎都是codehaus作品。知道cxf序列化和反序列化方式就比较容易解决问题啦。默认情况下cxf的jettison对泛型序列化存在问题,因为时间紧(一天就要做好restful webservice部署),没有具体去研究实现问题,我只是在之前使用过jackson,去处理json问题,而且cxf拥有jackson的MessageBodyReader和MessageBodyWriter实现类,我只要导入包并告诉cxf使用我指定的json provider就可以了,所以在客户端和服务器端双方都指定json privoder,jackson 库对json序列化实现非常的到位,异常的强大。我们都知道,只要java源码中指定的泛型类我没都可以反射出来,如果使用泛型站位符,就没法反射,因为java中的擦除法的原因(比如List<String>、List<T>,前面是清楚的指定泛型参数类型,后面一种是在运行时指定),我这里讨论的也是指定泛型参数类型情况下,jackson在这种情况下已经支持,所以不需要自己实现MessageBodyReader和MessageBodyWriter接口。如果是使用xml方式,除自己实现接口外,有更简单的方法,那就是在你的泛型类上面指定@XmlSeeAlso({某某类1.class,某某类2.class...})
问题2同样的比较简单,因为基于http的restful实现时,服务器返回数据的时候都会告诉客户端一个响应状态吗,就是我们常看到的200、404、500等,cxf框架的rs webservice客户端实现是通过判断状态大于等于300时,抛出异常webapplicationexception,所以如果服务器端有异常时,通过设置状态就可以实现,并返回Response(通过实现ExceptionMapper<E extends Throwable>接口,并加入到provider实现),如果客户端需要错误消息(这里不得不说jcp设计的jsr311比较的细腻),可以在Response中设置,客户端catch掉webapplicationexception异常,并可以读取错误消息。cxf到这里还没有完,cxf提供一个ResponseExceptionMapper接口,客户端实现这个接口并加入到provider中,客户端在调用的时候就不用去处理cxf的异常webapplicationexception,而是你自己接口的异常,因为客户端在调用webservice时,cxf创建调用接口的代理,代理在接收到300错误时,他知道服务器是返回webapplicationexception异常,他就是用你的ResponseExceptionMapper处理异常,因为这个接口中唯一方法fromResponse(Response r)返回的是一个异常。也就是说,实现这个类方法时,可以读取webapplicationexception中的Response所包含的消息,并要求返回一次异常对象。这样就达到客户端不用关心webapplicationexception异常而是关系自己接口上面声明的异常。
代码:
@XmlRootElement(name="Customer") public class Customer { private String id; private String name; private Date birthday; 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 Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } } @XmlRootElement(name="Me") public class Me implements Serializable { private String name; /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } } @XmlRootElement(name = "Page") @XmlSeeAlso({Customer.class,Me.class}) public class Page<T> implements Serializable{ /** * */ private static final long serialVersionUID = 5859907455479273251L; public static int DEFAULT_PAGE_SIZE = 10; private int pageSize = DEFAULT_PAGE_SIZE; // 每页的记录数 private long start; // 当前页第一条数据在List中的位置,从0开始 private List<T> data; // 当前页中存放的记录,类型一般为List private long totalCount; // 总记录数 /** * 构造方法,只构造空页. */ public Page() { this(0, 0, DEFAULT_PAGE_SIZE, new ArrayList<T>()); } public Page(int pageSize) { this(0, 0, pageSize, new ArrayList<T>()); } /** * 默认构造方法. * * @param start * 本页数据在数据库中的起始位置 * @param totalSize * 数据库中总记录条数 * @param pageSize * 本页容量 * @param data * 本页包含的数据 */ public Page(long start, long totalSize, int pageSize, List<T> data) { this.pageSize = pageSize; this.start = start; this.totalCount = totalSize; this.data = data; } /** * 取总记录数. */ public long getTotalCount() { return this.totalCount; } /** * 取总页数. */ public long getTotalPageCount() { if (totalCount % pageSize == 0) return totalCount / pageSize; else return totalCount / pageSize + 1; } /** * 取每页数据容量. */ public int getPageSize() { return pageSize; } /** * 取当前页中的记录. */ public List<T> getResult() { return data; } /** * 取该页当前页码,页码从1开始. */ public long getCurrentPageNo() { return start / pageSize + 1; } /** * 该页是否有下一页. */ public boolean hasNextPage() { return this.getCurrentPageNo() < this.getTotalPageCount(); } /** * 该页是否有上一页. */ public boolean hasPreviousPage() { return this.getCurrentPageNo() > 1; } /** * 获取任一页第一条数据在数据集的位置,每页条数使用默认值. * * @see #getStartOfPage(int,int) */ protected static int getStartOfPage(int pageNo) { return getStartOfPage(pageNo, DEFAULT_PAGE_SIZE); } /** * 获取任一页第一条数据在数据集的位置. * * @param pageNo * 从1开始的页号 * @param pageSize * 每页记录条数 * @return 该页第一条数据 */ public static int getStartOfPage(int pageNo, int pageSize) { return (pageNo - 1) * pageSize; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } /** * @return the start */ public long getStart() { return start; } /** * @param start the start to set */ public void setStart(long start) { this.start = start; } /** * @return the data */ public List<T> getData() { return data; } /** * @param data the data to set */ public void setData(List<T> data) { this.data = data; } /** * @param pageSize the pageSize to set */ public void setPageSize(int pageSize) { this.pageSize = pageSize; } /** * @param totalCount the totalCount to set */ public void setTotalCount(long totalCount) { this.totalCount = totalCount; } }
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = 7607640803750403555L; public ServiceException() { super(); }
public ServiceException(String message) { super(message); }
public ServiceException(String message, Throwable cause) { super(message, cause); }
public ServiceException(Throwable cause) { super(cause); } }
@Path(value = "/customer") @Produces({"application/xml","application/json"}) public interface CustomerService { @GET @Path(value = "/{id}/info") Customer findCustomerById(@PathParam("id")String id); @GET @Path(value = "/search") Customer findCustomerByName(@QueryParam("name")String name); @POST @Path(value = "/all") List<Customer> findAllCustomer(); @POST @Path(value = "/page") Page<Customer> findPageCustomer() throws ServiceException; @POST @Path(value = "/pageMe") Page<Me> findPage(); } public class CustomerServiceImpl implements CustomerService { public Customer findCustomerById(String id) { Customer customer = new Customer(); customer.setId(id); customer.setName(id); customer.setBirthday(Calendar.getInstance().getTime()); return customer; } public Customer findCustomerByName(String name) { Customer customer = new Customer(); customer.setId(name); customer.setName(name); customer.setBirthday(Calendar.getInstance().getTime()); return customer; } /** (non-Javadoc) * @see edu.xdev.restful.CustomerService#findAllCustomer() */ @Override public List<Customer> findAllCustomer() { List<Customer> tar = new LinkedList<Customer>(); Customer customer = new Customer(); customer.setId("e24234"); customer.setName("張三"); customer.setBirthday(Calendar.getInstance().getTime()); tar.add(customer); customer = new Customer(); customer.setId("324324"); customer.setName("李四"); customer.setBirthday(Calendar.getInstance().getTime()); tar.add(customer); return tar; } /** (non-Javadoc) * @see edu.xdev.restful.CustomerService#findPageCustomer() */ public Page<Customer> findPageCustomer() throws ServiceException { List<Customer> tar = new LinkedList<Customer>(); Customer customer = new Customer(); customer.setId("e24234"); customer.setName("張三"); customer.setBirthday(Calendar.getInstance().getTime()); tar.add(customer); customer = new Customer(); customer.setId("324324"); customer.setName("李四"); customer.setBirthday(Calendar.getInstance().getTime()); tar.add(customer); Page<Customer> page = new Page<Customer>(1, 2, 1, tar); if(1==1){ throw new ServiceException("abcd"); } return page; } /** (non-Javadoc) * @see edu.xdev.restful.CustomerService#findPage() */ public Page<Me> findPage() { List<Me> tar = new LinkedList<Me>(); Me m = new Me(); m.setName("中文"); tar.add(m); m = new Me(); m.setName("English"); tar.add(m); Page<Me> page = new Page<Me>(1, 2, 1, tar); return page; } }
@Provider public class InvokeFaultExceptionMapper implements ExceptionMapper<ServiceException> { /** (non-Javadoc) * @see javax.ws.rs.ext.ExceptionMapper#toResponse(java.lang.Throwable) */ @Override public Response toResponse(ServiceException ex) { ResponseBuilder rb = Response.status(Response.Status.INTERNAL_SERVER_ERROR); rb.type("application/json;charset=UTF-8"); rb.entity(ex); rb.language(Locale.SIMPLIFIED_CHINESE); Response r = rb.build(); return r; } } public class ServiceExceptionMapper implements ResponseExceptionMapper<ServiceException>{ /** (non-Javadoc) * @see org.apache.cxf.jaxrs.client.ResponseExceptionMapper#fromResponse(javax.ws.rs.core.Response) */ @Override public ServiceException fromResponse(Response r) { Object obj = r.getEntity(); ObjectMapper mapper = new ObjectMapper(); mapper.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); try { return mapper.readValue(obj.toString(), ServiceException.class); } catch (JsonParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (JsonMappingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return new ServiceException(obj.toString()); } } public class Server { /** * @param args */ public static void main(String[] args) { JAXRSServerFactoryBean factoryBean = new JAXRSServerFactoryBean(); factoryBean.getInInterceptors().add(new LoggingInInterceptor()); factoryBean.getOutInterceptors().add(new LoggingOutInterceptor()); factoryBean.setResourceClasses(CustomerServiceImpl.class); List<Object> list = new LinkedList<Object>(); list.add(new org.codehaus.jackson.jaxrs.JacksonJsonProvider()); list.add(new InvokeFaultExceptionMapper()); factoryBean.setProviders(list); factoryBean.setAddress("http://localhost:9000/ws/jaxrs"); factoryBean.create(); } } public class ClientTest { private static List<Object> getJacksonJsonProvider(){ List<Object> providers = new LinkedList<Object>(); providers.add(new ServiceExceptionMapper()); JacksonJsonProvider provider = new JacksonJsonProvider(); provider.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); providers.add(provider); return providers; } /** * @param args */ public static void main(String[] args) { try{ Page<Me> pages = getServiceInstance(CustomerService.class).findPage(); for(Me u:pages.getResult()){ System.out.println(u.getName()); } Page<Customer> page = getServiceInstance(CustomerService.class).findPageCustomer(); for(Customer u:page.getResult()){ System.out.println(u.getName()); } }catch(WebApplicationException e){ if(e instanceof WebApplicationException){ WebApplicationException we = (WebApplicationException)e; System.out.println(we.getMessage()); //System.out.println(we.getCause().getMessage()); } e.printStackTrace(); }catch(Exception e){ e.printStackTrace(); } } private static Map<Class<?>, Object> repos = new HashMap<Class<?>, Object>(); private static String baseUrl; static { baseUrl = "http://localhost:9000/ws/jaxrs"; } @SuppressWarnings("unchecked") public static <T> T getServiceInstance(Class<T> clazz){ T t = (T) repos.get(clazz); if(t==null){ synchronized (clazz) { t = (T) repos.get(clazz); if(t==null){ t = JAXRSClientFactory.create(baseUrl, clazz, getJacksonJsonProvider()); Client client = WebClient.client(t); WebClient.getConfig(client).getInInterceptors().add(new LoggingInInterceptor()); WebClient.getConfig(client).getInFaultInterceptors().add(new LoggingInInterceptor()); WebClient.getConfig(client).getOutFaultInterceptors().add(new LoggingOutInterceptor()); WebClient.getConfig(client).getOutInterceptors().add(new LoggingOutInterceptor()); client.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).acceptEncoding("UTF-8"); repos.put(clazz, t); } } } return t; } }
总结:
问题1:针对json格式使用jackson替换jettison库。针对xml格式,只要在指定泛型参数类上面同同过@XmlSeeAlso注解指定泛型参数类class即可。
问题2:通过ExceptionMapper接口和webapplicationexception异常实现,如果想更进一步可以加上ResponseExceptionMapper完成更舒坦的WebService设计
这里特别指出一下。MessageBodyReader、MessageBodyWriter、ExceptionMapper、webapplicationexception、XmlSeeAlso都是java规范中的api,ResponseExceptionMapper为cxf中的api。如果大家选择maven依赖管理cxf,注意cxf默认的jax-rs api依赖,其中2.7.4中默认依赖是javax.ws.rs-api-2.0-m10.jar,cxf2.5.10默认依赖是jsr311-api.1.1.1.jar。也就是说,要默认按照它依赖的jar,不要以为jax-rs 2.0的api还是m阶段,就降低api使用低版本正是版本jsr311-api.1.1.1.jar,这里在cxf中是有问题的。cxf官网上面明明说cxf目前实JAX-RS 1.1 and JAX-RS 1.0 (JSR-311),可实际已经开始支持jax-rs2版本,而jax-rs2 还没正式发布,所以cxf对jax-rs2实现自然就有问题。我开发时,被这里害惨啦,最终选2.5.10版本