一次动态代理的填坑之旅

栏目: 编程工具 · 发布时间: 7年前

内容简介:占小狼,转载请注明原创出处,谢谢!想在现有的接口加上熔断降级或者限流的功能,比较好的方式是通过注解的方式,并基于动态代理进行实现,下面代码是Rhino的实现通过在方法添加@Degrade注解,很方便的赋予了method方法熔断降级功能,在该方法的失败率达到阈值时,就自动熔断,并调用降级方法。

占小狼,转载请注明原创出处,谢谢!

一次动态代理的填坑之旅

背景

想在现有的接口加上熔断降级或者限流的功能,比较好的方式是通过注解的方式,并基于动态代理进行实现,下面代码是Rhino的实现

@Rhino
public class ServiceImpl {

    @Degrade(rhinoKey = "syncMethod-0",  fallBackMethod = "fallbackMethod")
    public void method() throws Exception {
        int i = 1 / 0;
    }

    private String fallbackMethod() throws Exception {
        return "fallback";
    }
}

通过在方法添加@Degrade注解,很方便的赋予了method方法熔断降级功能,在该方法的失败率达到阈值时,就自动熔断,并调用降级方法。

这里的动态代理并没有使用Spring的AOP,而是自己实现了 BeanPostProcessorBeanFactoryPostProcessor 接口,另外也实现了 PriorityOrdered 接口。

在生成动态代理对象的时候,根据类是否有实现接口,选择使用JDK的Proxy还是使用Cglib。

/**
 * @param clazz
 * @param origin
 * @return
 */
private static Object createProxyService(Class clazz, Object origin) {
    Object proxy;
    if (clazz.getInterfaces().length > 0) {
        proxy = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new RhinoInvocationHandler(origin, clazz));
    } else {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new RhinoInvocationHandler(origin, clazz));
        proxy = enhancer.create();
    }
    return proxy;
}

这样的实现,在99%的情况下,是没有问题的,直到有一天,有两个业务同时反馈了一个问题,使用了熔断降级的注解之后,发现自身的Spring AOP注解失效了或者是直接启动异常,WTF,这个问题一直都没考虑过好吗...

为什么会这样?Spring AOP到底干了什么,或者是自己的注解到底有什么问题?

复现问题

为了快速定位问题,并解决问题,最好的办法就是复现该问题,写了一个简单的Spring AOP的例子。

@Component  //加入到IoC容器
@Aspect  //指定当前类为切面类
public class Aop {

    @Pointcut("execution(* com.dianping.rhino.aop.*.*(..))")
    public void pointCut(){
    }

    @Before("pointCut()")
    public void begin(){
        System.out.println("begin");
    }

    @After("pointCut()")
    public void close(){
        System.out.println("close");
    }

    @Around(value = "@annotation(MethodLog)")
    public void around() {
        System.out.println(" MethodLog ");
    }
}

这里简短的解释下Spring AOP各个注解的作用

@Aspect

切面,标识该类是一个切面类

@Pointcut

切入点,用来标识哪些方法是需要被添加切面的

@Before

在切入点,执行方法之前进行增强

@After

在切入点,执行方法之后进行增强

@Around

在该例子中,只有添加了@MethodLog注解的方法才会被增强

一切准备就绪,开启DEBUG之旅,Spring的内部逻辑有点复杂,整个过程需要一点耐心。

一次动态代理的填坑之旅

通过Debug发现,Rhino的代理对象Processor排在Spring AOP的Processor,意味着Rhino生成的代理对象,会传给Spring AOP的Processor再做一层代理,在Spring AOP生成代理对象的内部逻辑中,有这么一段判断逻辑。

一次动态代理的填坑之旅

在createProxy方法中,会根据传入的beanClass,即上一个Processor处理过的对象,判断是否有实现接口。

回到Rhino的实现,因为ServiceImpl类没有实现接口,所以内部会采用CGLIB的方式创建代理对象,我们来看下这个对象的接口。

一次动态代理的填坑之旅

好家伙,默默的给加了一个Factory接口,这样在Spring AOP的处理中就当做有接口的情况进行实现了。这里最大的问题是,最终生成的代理对象是Factory类型的对象,在赋值给ServiceImpl变量时就会抛异常了。

解决问题

终于找到了问题的所在,那么改如何解决呢?

无法修改Spring AOP的逻辑,但是可以控制Rhino的逻辑,只需要把Rhino的Processor移到Spring AOP的Processor之后,这样就可以在Rhino的Proccessor中处理经过Spring AOP的代理过的beanClass对象了,有效的避免这个问题。

如何有效调整Processor的处理顺序?

通过分析发现Spring AOP的内部实现基于Ordered接口,而Rhino的实现是基于 PriorityOrdered 接口,而且处理器在初始化完成后会进行排序,实现 PriorityOrdered 接口的会放到前面,相同类型的再根据设置的order进行排序。很显然,Rhino的Processor被放在前面,找到了问题,解决方法也很简单,Rhino也换成Order接口,并且order设置成 LOWEST_PRECEDENCE ,即排在最后面。

结论

经过上面的调整之前,这个问题确实被有效的解决了,以前遇到Spring的异常,都是一脸懵逼。所以,遇到类似的问题,最好通过DEBUG源码去发现问题,并解决问题,这样可以有效的防止后续的继续挖坑。


以上所述就是小编给大家介绍的《一次动态代理的填坑之旅》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

DOM Scripting

DOM Scripting

Jeremy Keith / friendsofED / 2010-12 / GBP 27.50

There are three main technologies married together to create usable, standards-compliant web designs: XHTML for data structure, Cascading Style Sheets for styling your data, and JavaScript for adding ......一起来看看 《DOM Scripting》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换