面试官:cglib为什么不能代理private方法?原创
本文首发于公众号【看点代码再上班】,欢迎围观,第一时间获取最新文章。
一定要读的原文:
大家好,我是tin,这是我的第14篇原创文章
你用过Spring么?听说过Spring AOP么?肯定都有吧!
今天就说一说Spring AOP里面的一种代理方式cglib的原理,以及顺便回答一下cglib为什么不能代理private方法,先上一个目录:
-
-
二、cglib使用示例
-
三、cglib源码解析
-
四、结语
一、cglib是什么
cglib是一个开源的动态代码生成工具,其被广泛应用于AOP、测试、数据访问框架等生成动态代理对象和拦截方法字段访问,github地址是:https://github.com/cglib/cglib。其最新release版本是3.0.0
cglib作用是为没有实现接口的类提供代理的实现,这点区别于JDK动态代理,也算是JDK动态代理的一种补充。
这里说一下,在我们Spring AOP中,是同时采用了JDK动态代理和cglib两种代理实现,默认下是优先使用JDK动态代理,其次才会使用cglib,Spring doc官方文档介绍也表示了这点:
https://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上,可访问以下链接查看):
https://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,一个在努力让自己变得更优秀的普通工程师。自己阅历有限、学识浅薄,如有发现文章不妥之处,非常欢迎加我提出,我一定细心推敲并加以修改。
坚持创作不容易,你的正反馈是我坚持输出的最强大动力,谢谢!