returnvalue & xwrapper


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和List创建不同的子类IntegersWrapper和LongsWrapper。
为了客户端读取方便,所有子类中都提供一个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);
    }
  });
}

经过这样改造后,服务端和客户端的代码冗余都比以前减少很多,降低了维护成本。