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

本文首发于公众号【看点代码再上班】,欢迎围观,第一时间获取最新文章。
欢迎阅读原文:mp.weixin.qq.com/s/PDeE329ng…

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

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

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

一、cglib是什么

cglib是一个开源的动态代码生成工具,其被广泛应用于AOP、测试、数据访问框架等生成动态代理对象和拦截方法字段访问,github地址是:github.com/cglib/cglib… cglib作用是为没有实现接口的类提供代理的实现,这点区别于JDK动态代理,也算是JDK动态代理的一种补充。

这里说一下,在我们Spring AOP中,是同时采用了JDK动态代理和cglib两种代理实现,默认下是优先使用JDK动态代理,其次才会使用cglib,Spring doc官方文档介绍也表示了这点: docs.spring.io/spring-fram…

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上,可访问以下链接查看):
github.com/iam-tin/tin…

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

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

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

最后别忘了关注我哦!附上原文链接⏬⏬⏬
mp.weixin.qq.com/s/PDeE329ng…

猜你喜欢

转载自juejin.im/post/7053793796277927943