面试官:cglib为什么不能代理private方法?
本文首发于公众号【看点代码再上班】,欢迎围观,第一时间获取最新文章。
一定要读的原文: http://mp.weixin.qq.com/s/PDeE329ngo4bui-688PoPg
大家好,我是tin,这是我的第14篇原创文章
你用过Spring么?听说过Spring AOP么?肯定都有吧!
今天就说一说Spring AOP里面的一种代理方式cglib的原理,以及顺便回答一下cglib为什么不能代理private方法,先上一个目录:
-
二、cglib使用示例
-
三、cglib源码解析
-
四、结语
一、cglib是什么
cglib是一个开源的动态代码生成工具,其被广泛应用于AOP、测试、数据访问框架等生成动态代理对象和拦截方法字段访问,github地址是:http://github.com/cglib/cglib。其最新release版本是3.0.0
cglib作用是为没有实现接口的类提供代理的实现,这点区别于JDK动态代理,也算是JDK动态代理的一种补充。
这里说一下,在我们Spring AOP中,是同时采用了JDK动态代理和cglib两种代理实现,默认下是优先使用JDK动态代理,其次才会使用cglib,Spring doc官方文档介绍也表示了这点:
http://docs.spring.io/spring-framework/docs/6.0.x/reference/html/core.html#aop
cglib是通过动态生成代理类的子类实现代理功能 ,所生成的子类重写了代理类的所有方法(不包括final方法,至于private方法后文会讲到),cglib生成的子类中再通过方法拦截的方式实现对代理类方法的代码织入。
cglib底层采用字节码处理框架asm操作生成新的子类,下图可以比较直观地了解cglib相关的层次关系:
二、cglib使用示例
首先引入jar包:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
定义我们自己的业务类Developer:
再写一个方法拦截器DeveloperInterceptor:
方法拦截器实现了cglib的MethodInterceptor接口。
最后,通过cglib的字节码增强器即可生成新的代理子类:
package com.tin.example.cglib.cglib; import com.tin.example.cglib.design.pattern.SexEnum; import com.tin.example.cglib.design.pattern.employee.Developer; import net.sf.cglib.core.DebuggingClassWriter; import net.sf.cglib.proxy.Enhancer; /** * title: CglibTest * <p> * description: * * @author tin @公众号【看点代码再上班】 on 2022/1/15 下午5:00 */ public class CglibTest { public static void main(String args[]) { //输出cglib动态代理产生的类 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/ericli/**all-workshop/workspace/tin-example/cglib-class"); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Developer.class); //设置拦截器DeveloperInterceptor enhancer.setCallback(new DeveloperInterceptor()); //enhancer.create()生成代理类并强转为Developer,cglig生成的代理类命名格式:业务类名$$EnhancerByCGLIB$$... Object obj = enhancer.create(); System.out.println("proxy class:" + obj.getClass()); Developer developer = (Developer) obj; developer.setLines(30000); developer.setName("程序员@【看点代码再上班】"); developer.setNumber(1L); developer.setSex(SexEnum.MALE); developer.setSalary("¥15000"); System.out.println(developer.print()); } }
最后运行打印结果如下:
三、cglib源码解析
前面讲到cglib是通过生成代理类的子类实现代理功能,那么它又是怎么生成代理类的呢?我们通过源码一探究竟。
cglib的基本类关系图如下:
ClassGenerator是一个接口,也是cglib生成对象的最核心接口,它只有一个方法,方法的入参就是asm的ClassVisitor类,如下:
package net.sf.cglib.core; import org.objectweb.a**.ClassVisitor; public interface ClassGenerator { void generateClass(ClassVisitor v) throws Exception; }
我们上面示例代码CglibTest中是通过Enhancer.create()方法生成目标类的代理类的,我们从这里dubug进去看一下。
Enhancer.create方法调用了createHelper()方法,这个方法会再调用到AbstractClassGenerator的create()方法:
AbstractClassGenerator的create()方法初始化了ClassLoaderData对象:
ClassLoaderData是一个内部类,其包含了类加载器、生成的代理类等信息。进入到ClassLoaderData的构造器方法内:
构造器最终还是调用了AbstractClassGenerator的generate方法:
generateClassName方法定义了代理类名生成规则,就是我们很熟悉的命名格式:
……$$EnhancrByCGLIB$$……
DefaultGeneratorStrategy#generate()方法返回了一个字节数组,其内部就使用到了asm字节码处理框架,继续跟进去:
在Enhancer内,重写了generateClass()方法,该方法通过asm操作class字节码并生成新的类(也就是我们的cglib代理类)。截部分源码图如下:
到此,源码基本走完了,还记得我的测试类main方法中加的下面这行代码么:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/ericli/**all-workshop/workspace/tin-example/cglib-class");
它是用来把cglib生成的代理类的class文件输出到本地磁盘的,如果没有配置,默认只会保存在JVM内。已经生成了,我们看下生成的cglib代理类长什么样(代理类实在是太长了,只能放到git上,可访问以下链接查看):
http://github.com/iam-tin/tin-example/blob/mast……
关于冗长的代理类,我只说两点重点吧:
-
1、代理类继承了业务类
-
2、代理类作为子类,重写了业务类所有可以重写的方法,这些重写的方法也是代理类真正调用的方法
比如重写业务类中的accept方法,会先判断是否有配置拦截器,有的话会执行拦截方法:
说到这,重点来了,cglib实质上是通过继承父类并重写父类的方法达到生成代理类的,那么自然的,final类和final方法一定无法通过cglib代理,在生成的class文件中也不会找到对应的final方法。
既然如此,private方法呢?我们知道子类是无法访问父类的private方法的,但是我们可以在子类写一个和父类一模一样的方法呀!这样不就可以了么?为什么cglib不这么干呢?
这个问题,已经脱离了cglib本身,实际是一个Java基础知识点。 Java中父类中的方法是private的,子类不可访问,即使子类实现一个和父类一模一样的方法,实际上只是属于子类的新方法,并不会覆盖父类对应的方法。
比如下面BClass继承了AClass,BClass“重写了”AClass的私有方法print():
/** * title: AClass * <p> * description: * * @author tin @公众号【看点代码再上班】 on 2022/1/16 下午6:13 */ public class AClass { private void print() { System.out.println("A"); } public static void main(String[] args) { AClass a = new BClass(); a.print(); BClass b = new BClass(); b.print(); new BClass().print(); } } class BClass extends AClass { public void print() { System.out.println("B"); } }
看看结果输出了什么:
如果把AClass的print()方法改为public,结果就不一样了:
这就是最后的结论:cglib代理类无法访问业务类的私有方法,也就无法代理业务类的私有方法了。
四、结语
我是tin,一个在努力让自己变得更优秀的普通工程师。自己阅历有限、学识浅薄,如有发现文章不妥之处,非常欢迎加我提出,我一定细心推敲并加以修改。
坚持创作不容易,你的正反馈是我坚持输出的最强大动力,谢谢!
- 面试官:cglib为什么不能代理private方法?
- 想看Dubbo源码?一定要先看一看这一篇!
- 死磕synchronized二:系统剖析延迟偏向篇一
- 架构与思维:高并发下解决主从延时的一些思路
- 道与术
- OopMap看不懂,怎么调优哇
- Kafka 精妙的高性能设计(下篇)
- 一次tcp窗口被填满问题的排查实践
- 抢了个票,还以为发现了12306的系统BUG
- 微服务5:服务注册与发现(实践篇)
- 看一遍就理解:零拷贝详解
- 分布式:分布式系统下的唯一序列
- 面试官:为什么jdk动态代理只能代理接口实现类?
- 揭开内存屏障的神秘面纱
- 微服务4:服务注册与发现
- 我就奇了怪了,STW到底是怎么做到的
- 这样使用 IDEA ,效率提升10倍!| IDEA 高效使用指南
- 从hotspot源码层面剖析Java的多态实现原理
- 垃圾回收全集之十二:GC 调优的实战篇—Weak, Soft 及 Phantom 引用
- JVM的多态是如何实现的