先交待背景,項目使用了HSF框架,Pandora容器,此項目我方作為生產者提供服務給其他第三方調用,在測試過程中,發現接口有不盡人意的地方,以下為具體描述。
假定A作為服務生產者,B作為服務消費者,A提供有接口方法addEntityFile,定義如下
ResultJsonEO<List<EntityResultEO>> addEntityFileJson(EntityFileInputDTO inputDTO);
其中,ResultJsonEO結構如下
public class ResultJsonEO<T>{
public int code;
private String msg;
/**
* 實體對象
*/
private T data;
public String getMsg() {
return msg;
}
public ResultJsonEO<T> setMsg(String msg) {
this.msg = msg;
return this;
}
public T getData() {
return data;
}
public ResultJsonEO<T> setData(T data) {
this.data = data;
return this;
}
public ResultJsonEO<T> setCode(ResultCode resultCode) {
this.code = resultCode.code;
return this;
}
public int getCode() {
return code;
}
public ResultJsonEO<T> setCode(int code) {
this.code = code;
return this;
}
/**
* 調用成功
* @return
*/
public boolean isSuccess() {
return ResultCode.SUCCESS.code == this.code;
}
}
對於調用方來說,根據返回的json對象(泛化)可以得知,不論提供方內部如何異常,能拿到的總是ResultJsonEO對象,並能通過isSuccess()方法得知返回結果是否正常。 實際上,在測試時,我們發現服務內部發生異常后,對方得不到結果,只得到如下異常提示,非常不友好。
java.util.HashMap cannot be cast to xx.common.api.domain.eo.ResultJsonEO
剛開始看到這個報錯的時候,很疑惑,因為找遍了代碼也沒有找到哪里有HashMap轉json對象的這種操作,最后才發現這壓根就是hsf框架將異常泛化后拋出的,終究還是因為服務內部沒有針對異常進行捕獲或拋出。
這里補充說明一下,如果不考慮統一返回json對象,其實可以做一個全局異常處理,一旦遇到了異常,就統一拋出異常信息,只不過這時對於消費者來說,調用此接口方法時,就需要額外的異常處理。
找到了問題原因所在,接下來就是針對性處理。假如是一個傳統的ssh項目,或者是一個單純的springCloud項目(沒有用到這種rpc框架),大體結構如下
controller
service
dao
domain
通常都是在service層處理業務邏輯,這一層也負責事務處理,而且在service層是不會進行異常捕獲的,避免中斷spring事務,異常捕獲都是放在controller層里處理,通常都有統一的全局異常捕獲處理。此項目中,報上述錯誤時的大體結構如下
apiservice 接口定義層
apiserviceImpl 接口實現層,主要的業務邏輯均在里面,包括事務處理
dao
domain
首先,想到了一種解決方案,即將apiserviceImpl層拆分,拆分為apiserviceImpl層和service層,主要的邏輯和事務管理均在service層中完成,apiserviceImpl充當一個類似controller層的角色(注意,這里如果把apiserviceImpl充當controller層來看時,service層是一定要拆分出去的,假如不拆分,直接針對apiserviceImpl進行異常捕獲,碰到事務時,一旦發生異常,事務就會被中斷)。
apiservice
apiserviceImpl
service 專門處理業務邏輯,事務
dao
domain
這樣做的好處在於層次清晰,也可以使用全局異常處理,切面面向impl層,唯一的弊端在於所有的impl代碼都得調整,進行代碼拆分和service注入,改動稍微有點大。
最后,采用了Filter的方式,只需要增加一個Filter,繼承hsf原有的Filter的onResponse方法。
import com.taobao.hsf.invocation.filter.ServerFilter;
public class HsfServerFilter implements ServerFilter {
/**
* @param invocation
* @param rpcResult
*/
@Override
public void onResponse(Invocation invocation, RPCResult rpcResult) {
Class returnType = invocation.getReturnClass();
Object appResponse = rpcResult.getAppResponse();
if(appResponse instanceof Exception) {
Object re = rpcResult.getAppResponse();
log.error("methodName:"+invocation.getMethodName()
+"\tmethodArgs:"+invocation.getMethodArgs().toString()
+"\tmethodException:"+re.toString());
if(returnType.getName().indexOf("ResultDataEO") > -1){
rpcResult.setAppResponse(ResultDataEO.fail("服務異常,請聯系管理員......"));
}else{
rpcResult.setAppResponse(ResultJsonResponse.responseErr("服務異常,請聯系管理員......"));
}
}
}
}
onResponse方法,無論接口實現類中是否執行成功,此方法總是會執行的,只不過執行成功的情況下,getAppResponse()得到的是正確的返回類,比如ResultJsonEO,失敗的情況下則是異常。上面的解決方式則是異常情況下,強制將返回的信息賦值進去。
這樣一來,代碼改動量不大,而且也不會影響事務(onResponse方法區別於前面提到的aop切面實現的方式,切面方式相當於是把實現類里的代碼try catch了,而filter中的onResponse方法是在實現類中的方法執行完之后才會進入)。
