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(Function0f) { 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); } }); }
经过这样改造后,服务端和客户端的代码冗余都比以前减少很多,降低了维护成本。