最近在項目中,由於幾個系統本身須要數據交換,因此採用進來都比較流行的RestFul風格WebService,實現框架採用apache的cxf,apache的東西一直以來都是比較的好用,回話少說,進入正題。 java
首先,提出兩個問題,已經解決方案,後面跟上代碼。 web
一、cxf中如何實現java中泛型的數據序列化和反序列化(一般使用json、xml格式,cxf默認不支持泛型) 數據庫
二、cxf後臺異常,在前臺如何反映 apache
問題1答案比較簡單,cxf中的數據序列化是能夠替換掉使用你實現MessageBodyReader<Object>和MessageBodyWriter<Object>接口就能夠啦,針對xml,cxf採用stax二、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...}) json
問題2一樣的比較簡單,由於基於http的restful實現時,服務器返回數據的時候都會告訴客戶端一個響應狀態嗎,就是咱們常看到的200、40四、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異常而是關係本身接口上面聲明的異常。 api
代碼: 服務器
@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 { restful
private static final long serialVersionUID = 7607640803750403555L; public ServiceException() { super(); } app
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版本