先交待背景,项目使用了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方法是在实现类中的方法执行完之后才会进入)。