一次动态代理的填坑之旅
背景
想在现有的接口加上熔断降级或者限流的功能,比较好的方式是通过注解的方式,并基于动态代理进行实现,下面代码是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,而是自己实现了BeanPostProcessor和BeanFactoryPostProcessor接口,另外也实现了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源码去发现问题,并解决问题,这样可以有效的防止后续的继续挖坑。
欢迎工作一到五年的Java程序员朋友们加入Java架构开发:744677563
本群提供免费的学习指导 架构资料 以及免费的解答
不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
17.移动架构手写数据库增删改查框架
待续。。。
- 下一篇
「架构技术专题」什么是架构设计的五个核心要素?(3)
架构中五个重要的核心指标:分别是性能、可用性、伸缩性、扩展性和安全性。 一、性能 性能就是核心要素之一,不然我为什么架构设计?随随便便一个lowlow的系统上线就好了。所以性能优化是很多小公司卖不去过的坎。这么说吧,当然优化网站性能的手段也非常多: (1)web前端性能优化: 浏览器访问优化(浏览器缓存、页面压缩传输、合理布局页面、减少Cookie传输) 减少http请求。避免建立太多通讯链路。将js、css、图片文件尽可能合并。避免太多请求。同时,对于系统的后端请求也尽可能进行合理的设计,来避免出现太多交互。 使用浏览器的缓存。http头设置Cache-Control和Expires.js文件名比如可以带时间戳。一旦有更新则更新时间戳,否则缓存;同时尽量避免同一时间更新大量静态资源。 对静态资源进行压缩。 css放置在页面最上方,js放下最下面。以提前进行css渲染。同时避免js带来的页面阻塞。但需要case by case。比如页面dom节点需要依赖js生成,则可视情况改变文件位置。 减少cookie传输。同时让静态资源有独立域名,发送静态资源请求时候不发送cookie。以此减少传...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Hadoop3单机部署,实现最简伪集群
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果