转载自  一次动态代理的填坑之旅

背景

想在现有的接口加上熔断降级或者限流的功能,比较好的方式是通过注解的方式,并基于动态代理进行实现,下面代码是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源码去发现问题,并解决问题,这样可以有效的防止后续的继续挖坑。

一次动态代理的填坑之旅相关推荐

  1. React Native填坑之旅--动画篇

    React Native填坑之旅--Button篇 React Native填坑之旅--动画 React Native填坑之旅--HTTP请求篇 动画是提高用户体验不可缺少的一个元素.恰如其分的动画可 ...

  2. Jenkins项目实战之-MacOS High Sierra自动化打包方案的填坑之旅

    前面我介绍了如何在android和iOS实现公司内部app的自动化打包构建的过程.这里写一个关于这个app自动化打包平台从想法到实践再到放机房平稳运行的一个回忆录.总的来说,在jenkins上实现an ...

  3. 【填坑之旅】手把手教你如何用AndroidStudio上传自己的library到Jcenter库

    [填坑之旅]手把手教你如何用AndroidStudio上传自己的library到Jcenter库 前言:我们在使用AndroidStudio的时候,会经常用到第三方的代码库.引入第三方库的方式无非就是 ...

  4. 《Getting Started with D3》填坑之旅(六):第三章(下)

    Chapter 3. Scales, Axes and Lines(比例尺.坐标轴与线) (接上篇:<Getting Started with D3>填坑之旅(五):第三章(上)) 示例2 ...

  5. AppCode Updating indexes一直不停的填坑之旅

    最近在做APP安装包瘦身,找到了传说中的AppCode神器. AppCode 提供了 Inspect Code 来诊断代码,其中含有查找无用代码的功能.它可以帮助我们查找出 AppCode 中无用的类 ...

  6. [动态代理三部曲:上] - 动态代理是如何坑掉了我4500块钱

    前言 不知道,起这个名字算不算是标题党呢?不过如果小伙伴们可以耐心看下去,因为会觉得不算标题党~ 这是一个系列文章,目的在于通过动态代理这个很基础的技术,进而深入挖掘诸如:动态生成class:Clas ...

  7. 关于Echarts的填坑之旅

    正如标题所说,这是Echarts的一遍填坑,如果你是一些echart的配置的话可以阅读 http://echarts.baidu.com/opti...的官网配置信息.今天我想给大家分享的是一些我前段 ...

  8. 微信云托管-填坑之旅

    微信云开发开始收费了,一个月20块钱,贼贵,用不起.而按用量收费.资源还能复用的兄弟产品微信云托管还像还行.所以就有了一场填坑旅行. windows10下安装docker之坑 在本地开发,需要使用do ...

  9. ViewPager中显示图片和播放视频填坑之旅

    一.需求来源与实现思路 1.最近项目需求中有用到需要在ViewPager中播放视频和显示图片的功能,视频是本地视频,最开始的实现思路是ViewPager中根据当前item位置对应的是图片还是视频去初始 ...

最新文章

  1. 工科学生懂艺术,魔鬼神仙挡不住
  2. 自然语言处理 的 一些环境和包
  3. 第二十八讲 解非齐次线性方程组
  4. 微信小程序实现循环列表下拉功能(点击事件)
  5. linux之lrzsz
  6. 【OS学习笔记】二十三 保护模式七:保护模式下任务的隔离与任务的特权级概念
  7. 特斯拉股价周五收盘下跌7.38% 此前公布第三季度交付量创新高
  8. Selenium XPath示例
  9. python中的多线程(一)
  10. 编译器之词法分析器(Lexical Analyzer)
  11. hsqldb mysql_安装HSQLDB
  12. 【统计信号处理检测理论:CFAR检测】
  13. 陶哲轩实分析 5.2 节习题试解
  14. 大二第六周(逐渐暴躁的一周)
  15. mysql按每月分组统计_MySql按周,按月,按日分组统计数据
  16. 目标跟踪介绍(单目标)
  17. Apollo Cyber实践之talker-listener通信
  18. 微信小程序和公众号开发流程
  19. 如何导出、导入mysql数据
  20. JZOJ-senior-5934. 【NOIP2018模拟10.29】列队

热门文章

  1. 栈的基础概念与经典题目(Leetcode题解-Python语言)
  2. 你知道304吗?图解强缓存和协商缓存
  3. [PAT乙级]1016 部分A+B
  4. [Java基础]HashSet集合概述和特点
  5. C++to_string应用举例
  6. Facade(外观)--对象结构型模式
  7. 中获取文件名不要扩展名_如何批量修改文件名,3s时间够不够
  8. zookeeper理解
  9. AC Automaton
  10. Loj #6274. 数字 数位dp + 去重