应用里有较多的地方访问外部供应商接口,由于公网网络不稳定或者外部接口不稳定(重启,发版,ip切换)的原因,经常报502或者504错误。为了解决HTTP调用的500报错,选择使用spring的retryable注解进行重试。给所有的RestTemplate调用的函数都加上了@Retryable注解,本地测试发现好多都不起效果。
网上搜索有很多帖子反馈@Retryable注解不生效,不生效的点在于:切面切的方法,是在所在的类里被另外的方法调用的,而不是被外部类直接调用。比较难以理解,直接上代码演示:
public class AopClass {
public void methodA() {
methodB();
}
@Retryable
public void methodB() {
System.out.println();
}
}
public class Client{
public static void main(String[] args) {
AopClass aop = new AopClass();
// 切面不会起效
aop.methodA();
}
}
不仅仅是@Retryable有这个问题,所有的切面实现的功能,如果这样调用都有问题。这里通过分析cglib的动态生成的字节码类来分析:
aop代理类
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("前置代理");
//通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置代理");
return result;
}
}
被代理类
public class SayHello {
public void callee() {
System.out.println("hello everyone");
}
public void caller() {
this.callee();
}
}
配置好-Dcglib.debugLocation参数
查看动态生成的类(第三个)
通过反编译工具查看class文件
public class SayHello$$EnhancerByCGLIB$$11ca72e0 extends SayHello implements Factory {
private MethodInterceptor CGLIB$CALLBACK_0;
public final void caller() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
// 如果被代理
if (var10000 != null) {
var10000.intercept(this, CGLIB$caller$0$Method, CGLIB$emptyArgs, CGLIB$caller$0$Proxy);
} else {
// 如果没被代理
super.caller();
}
}
}
通过生成的类可以发现,如果直接调用没被代理的方法,那么调用的是父类SayHello的方法,也就是没被加强的方法。