introduction
背景是WebService,具体实现是XFire。由于某种原因XFire的Fault无法使用,导致只能使用返回值表示成功与错误的情况。举例如下:
// return type
class IntWrapper {
boolean success;
String message;
int value;
}
// client
IntWrapper wrapper;
if(wrapper == null) throw new NullPointException("wrapper is null");
if(!wrapper.success) throw new Exception(wrapper.message);
return wrapper.value;
实际实现时,发现服务有很多方法,每个方法有各自的返回类型,有部分重复。其次是服务端每个方法都必须try catch,把错误转化为返回值。第三是客户端代码也有重复,每次都要检查是否为null和是否成功。
为了减少重复代码,提供如下方案:
design & implementation
服务器端,抽取公共参数,success,message,分别代表是否成功和错误信息,即类ReturnValue。不同的返回类型创建不同的子类,比如返回类型为int的,创建IntWrapper,继承ReturnValue。如果你想问是否可以用泛型?答案可能是不行,原因是XFire的限制。所以你可能要为List
为了客户端读取方便,所有子类中都提供一个getValue()方法,无参,返回类型不同,返回内容即包裹前的返回值。比如IntWrapper,getValue方法返回类型为int,内容为被包裹的int型内容。
其次,XFire需要无参构造函数和setXXX方法。第三,部分方法没有返回,这里创建一个VOID的静态常量,表示正常结果。第四,为了拦截器和服务代码的方便,提供多个构造函数。实际代码大致如下:
import java.io.Serializable;
public class ReturnValue implements Serializable {
public static final ReturnValue VOID = new ReturnValue();
private static final long serialVersionUID = 6425810571665880741L;
private boolean success;
private String message;
public ReturnValue() {
this(true, null);
}
public ReturnValue(String message) {
this(false, message);
}
private ReturnValue(boolean success, String message) {
super();
this.success = success;
this.message = message;
}
public boolean isSuccess() {
return success;
}
public String getMessage() {
return message;
}
public void setSuccess(boolean success) {
this.success = success;
}
public void setMessage(String message) {
this.message = message;
}
}
public class IntWrapper extends ReturnValue {
private static final long serialVersionUID = -2480241630737073363L;
private int value;
public IntWrapper() {
super();
}
public IntWrapper(String message) {
super(message);
}
public IntWrapper(int value) {
super();
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
以上是返回类型,接下来如何去除过多的try catch的。这里使用AOP的MethodInterceptor,在拦截器中try catch,在异常的时候通过反射取得返回类型(ReturnValue的子类)的构造方法,实例化一个表示错误的对象并返回。核心代码如下:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class Interceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
return invocation.proceed();
} catch (Throwable t) {
return createReturnValue(invocation.getMethod().getReturnType(),
"runtime error, cause is [" + t.getMessage() + "]");
}
}
private Object createReturnValue(Class<?> returnType, String message) throws Exception {
Constructor<?> constructor = returnType.getConstructor(String.class);
return constructor.newInstance(message);
}
}
做了这两部之后,把环境搭起来,配置好Spring,服务代码就可以这样写了:
public IntWrapper countFoo() {
return new IntWrapper(barService.countFoo());
}
是不是很简单?
接下来是客户端的处理。客户端通用的逻辑有很多,唯一的不同是调用服务端的哪个方法。抽象出来的辅助类如下:
public interface Function0{ T apply(); }
class ReturnValueHelper {
public Object getValue(Function0 f) {
ReturnValue value = getReturnValue(f);
checkReturnValue(value);
return invokeGetValue(value);
}
private Object invokeGetValue(Object obj) {
try {
return obj.getClass().getMethod("getValue").invoke(obj);
} catch (Exception e) {
throw new RuntimeException("failed to invoke getValue", e);
}
}
public void checkIfSuccess(Function0 f) {
checkReturnValue(getReturnValue(f));
}
private void checkReturnValue(ReturnValue value) {
if (value == null) throw new RuntimeException("return value is null");
if (!value.isSuccess()) throw new RuntimeException(value.getMessage());
}
private ReturnValue getReturnValue(Function0 f) {
try {
return f.apply();
} catch (Exception e) {
throw new RuntimeException("failed to apply", e);
}
}
}
关注两个方法,一个是getValue,另一个是checkIfSuccess。前者针对有返回值的方法,后者是原先无返回值的方法。客户端使用如下:
private ReturnValueHelper returnValueHelper = new ReturnValueHelper();
public int count() {
return (Integer) returnValueHelper.getValue(new Function0<ReturnValue>() {
public ReturnValue apply() {
return fooService.count();
}
});
}
public void remove(final Long id) {
returnValueHelper.checkIfSuccess(new Function0<ReturnValue>() {
public ReturnValue apply() {
return barService.remove(id);
}
});
}
经过这样改造后,服务端和客户端的代码冗余都比以前减少很多,降低了维护成本。