面试官:cglib为什么不能代理private方法?

16 篇文章 0 订阅
12 篇文章 0 订阅

本文首发于公众号【看点代码再上班】,欢迎围观,第一时间获取最新文章。

一定要读的原文:https://mp.weixin.qq.com/s/PDeE329ngo4bui-688PoPg

欢迎阅读原文:

面试官:cglib为什么不能代理private方法?cglib实质上是通过继承父类并重写父类的方法达到生成代理类的,那么自然的,final类和final方法https://mp.weixin.qq.com/s/PDeE329ngo4bui-688PoPg

大家好,我是tin,这是我的第14篇原创文章

你用过Spring么?听说过Spring AOP么?肯定都有吧!

今天就说一说Spring AOP里面的一种代理方式cglib的原理,以及顺便回答一下cglib为什么不能代理private方法,先上一个目录:

一、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/small-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.asm.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/small-workshop/workspace/tin-example/cglib-class");

它是用来把cglib生成的代理类的class文件输出到本地磁盘的,如果没有配置,默认只会保存在JVM内。已经生成了,我们看下生成的cglib代理类长什么样(代理类实在是太长了,只能放到git上,可访问以下链接查看):

https://github.com/iam-tin/tin-example/blob/master/cglib-test/src/main/java/com/tin/example/cglib/cglib/cglib-class-example

关于冗长的代理类,我只说两点重点吧:

  • 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,一个在努力让自己变得更优秀的普通工程师。自己阅历有限、学识浅薄,如有发现文章不妥之处,非常欢迎加我提出,我一定细心推敲并加以修改。

坚持创作不容易,你的正反馈是我坚持输出的最强大动力,谢谢!

最后别忘了关注我哦!⏬⏬⏬

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值