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

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

3674325

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

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

 

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

点赞收藏
看点代码再上班
请先登录,查看3条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

【全网首发】(大表小技巧)有时候 2 小时的 SQL 操作,可能只要 1 分钟

【全网首发】(大表小技巧)有时候 2 小时的 SQL 操作,可能只要 1 分钟

一次java内存top res高排查记录

一次java内存top res高排查记录

JAVA中计算两个日期时间的差值竟然也有这么多门道

JAVA中计算两个日期时间的差值竟然也有这么多门道

干货!Java代码优化必知的30个小技巧!

干货!Java代码优化必知的30个小技巧!

Java 异步调用原理与实战

Java 异步调用原理与实战

没有二十年功力,写不出Thread.sleep(0)这一行“看似无用”的代码!

没有二十年功力,写不出Thread.sleep(0)这一行“看似无用”的代码!

25
3