JAVA动态代理和Annotation实现方法自动重做的实践
分布式系统的一个重要问题就是分布式的事务,在没有分布式事务的前提下为了保证各个系统间数据的一致性比较简单易行的方法就是重试和补偿了。最近公司系统也遇到了这样的问题,远程调用超时或者消息消费失败之后造成了一些脏数据,为了给这些点统一加上重做机制首先就想到了Java的动态代理,如果稍微再能够灵活配置一点简单的做法就是加annotation。自己先写了一个简单的demo。
?
1。为了能够进行一些简单的配置化,定义了一个annotation。其中
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@interface AutoRedo {int redoTimes() default 1;String taskNameOnError();}
?
2。假如我们要动态代理的类和接口如下:(java本身的代理机制只支持接口的动态代理,如果要对类做动态代理可以使用cglib)。所以这个annotation一定要加在接口上,类上注解在代理上是不起作用的。
interface Subject {@AutoRedo(redoTimes = 2, taskNameOnError = "xxRedoTask")public void request();}class RealSubject implements Subject {public void request() {System.out.println("RealSubject request()!");}}
?
3。代理的时候要做的事情:
class DynamicProxy implements InvocationHandler {private Object obj;public DynamicProxy(Object obj) {this.obj = obj;}public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {int redoTimes = 0;int failCnt = 0;String taskNameOnError;if (method.isAnnotationPresent(AutoRedo.class)) {redoTimes = method.getAnnotation(AutoRedo.class).redoTimes();taskNameOnError = method.getAnnotation(AutoRedo.class).taskNameOnError();}for (int i = 1; i <= redoTimes+1; i++) {try {method.invoke(this.obj, args);break;} catch (Exception e) {failCnt++;}}if (failCnt == redoTimes+1) {// insert asynTask with name: taskNameOnError}return null;}}?
4。最后写一个工厂来方便调用,因为java.lang.reflect.Proxy已经做了cache,保证同一个interface的proxy只动态生成一个。所以工厂就不做cache了,这样也不会出现持久带内存一直增长的问题。(无节制的生成动态class而产生的频发fullgc需要小心)
class RedoInterceptorFactory {@SuppressWarnings("unchecked")public static <T> T getInterceptedInstance(T target) {InvocationHandler h = new DynamicProxy(target);return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), h);}}
?
5。真正的调用端
public static void main(String[] args) {Subject realSubject = new RealSubject();RedoInterceptorFactory.getInterceptedInstance(realSubject).request();}
?
参考资料: