使用CXF開發RestFul WebService問題解決方案

      最近在項目中,由於幾個系統本身須要數據交換,因此採用進來都比較流行的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版本

相關文章
相關標籤/搜索