当前位置: 首页 > article >正文

代理动态代理

代理

1.这是什么?

  • 假设你想租一套房子。房东提供了房子,但你并不直接与房东接触,而是通过房屋中介来进行。房屋中介在这个过程中充当了一个代理的角色。
  • 银行自动提款机。ATM 机代表银行进行一些实际操作,而不直接暴露银行的核心系统。ATM 机通过代理模式,控制了访问银行核心交易系统的权限,确保了账户信息的安全,并且提供了客户可以操作的界面。
  • 网络代理(如VPN)是现代互联网使用中常见的代理模式应用,尤其在保护用户隐私和绕过网络限制方面。

这些例子都展示了代理模式如何在不同的场景中提供了对“真实对象”的间接控制。通过上面这些例子,可以看到“代理”大致有三个角色:客户端(使用者)、代理(中间层)、真实对象(交付委托的对象、目标对象)。

在这里插入图片描述

为什么要使用代理呢?

  • 控制访问权限、增加安全性: 代理模式可以控制客户端对真实对象的访问,尤其在需要做权限控制时非常有用。例如atm机,仅提供几个有限的功能给用户使用,其他的功能用户想用也用不了。
  • 逻辑增强: 代理对象可以在真实对象的基础之上,增加额外的逻辑。例子:租房中介(房东发布房源,中介可以在房源信息的基础之上增加看房、筛选、签合同、确认合同条款等等逻辑,房东就不用操心这些)。在编程代码中,代理模式能够将客户端和真实对象解耦。客户端通过代理访问真实对象,而不需要知道实际对象的细节。这种解耦有助于系统的维护和扩展。

通过使用代理模式,系统能够在不直接暴露真实对象的情况下,提供更多的控制和优化,这对于实际的软件设计和架构非常重要。

2.静态代理

静态代理是代理模式的一种实现方式,其中代理类在编译时就已经确定,代理类和真实类都会实现相同的接口(或者继承相同的类),通过代理类来控制对真实对象的访问。静态代理的“静态”指的是代理类是在编译时就已经创建好了,代理类的行为是固定的。

静态代理的结构:

  1. Subject(主题接口):定义了真实对象和代理类共同实现的接口,通常包含一个或多个方法。
  2. RealSubject(真实主题):实现了Subject接口,是真正需要被代理的对象。
  3. Proxy(代理类):实现了Subject接口,内部持有对RealSubject对象的引用,通过该引用来控制对真实对象的访问,并可以在调用之前或之后加入额外的操作(如日志记录、权限验证等)。
  4. User(也就是我们所说的客户端,就是调用的)

Subject (出租房子)

// Subject接口,定义了真实对象和代理类要实现的行为
public interface Subject {
    void request();
}

RealObject (房东)

// RealSubject实现了Subject接口,是真正的业务实现类
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

Proxy (中介)

// Proxy类也实现了Subject接口,并在请求前后添加额外的操作
public class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy() {
        this.realSubject = new RealSubject();  // 创建真实对象
    }

    @Override
    public void request() {
        // 在调用真实对象的方法之前,可以加入一些额外的操作
        System.out.println("Proxy: Before");
        // 调用真实对象的方法
        realSubject.request();
        // 在调用真实对象的方法之后,也可以添加额外的操作
        System.out.println("Proxy: After");
    }
}

User (使用者)

public class Client {
    public static void main(String[] args) {
        // 客户端使用Proxy类来访问请求
        Subject subject = new Proxy();  // 使用代理类
        subject.request();  // 调用代理方法
    }
}

可以看到其实现特别简单,代理对象持有一个真是对象的实例,然后调用真实对象的方法就可以了。如果,真实对象想要新增一个功能呢?那么,Subject 、RealObject 、Proxy 都会动代码

优点:

  • 实现简单

缺点

  • 代理类与真实类具有相同的接口,每增加一个代理类就需要创建一个相应的代理类,增加了代码的重复性和维护的难度。

可以看到,静态代理适用于简单的系统,当代理逻辑固定、代理类数量不多时,静态代理是一个简单且有效的方案。

3.动态代理

①初步

动态代理是代理模式的另一种实现方式,它与静态代理不同,代理类是在运行时动态生成的,而不需要在编译时就确定。

说到运行时,这不马上就想到反射了吗?

我们首先来运用反射,来自定义一个动态代理。

思想: 客户端只需要选择调用什么方法,传哪些参数就可以了

// 功能
public interface Subject {
    String takeMoney(String uid); // 取钱的功能
    
    // void putMoney(Integer num); // 存钱的功能
}

银行

// 银行
public class Bank implements Subject{
    @Override
    public String takeMoney(String uid) {
        return uid + " 取出 200 元";
    }
    
    /*
    @Override
    public void putMoney(Integer num) {
        System.out.println("存钱: " + num + "元");
    }
    */
}

atm机

import java.lang.reflect.Method;
// ATM机
public class ATM {
    private Class<Bank> typeClass;
    private Bank bank;
    public ATM() {}
    public ATM(Class<Bank> typeClass) {
        this.typeClass = typeClass;
    }
    private Object invoke(String methodName, Object... args) {
        Class<?>[] paramsType =  new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            paramsType[i] = args[i].getClass();
        }
        // 功能名, 参数列表
        if ( typeClass != null ){
            // 创建一个银行对象
            try {
                if ( this.bank ==  null ) this.bank = typeClass.newInstance(); // 如果是空的,创建一个实例
                Method method = typeClass.getMethod(methodName, paramsType);
                return method.invoke(this.bank, args);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return "ERROR";
        } else {
            System.out.println("【ATM机没弄好】~~~~~~");
            return "ERROR";
        }
    }
    public Object atmProcess(String methodName, Object... args) {
        System.out.println("【尊贵的客户您好,atm为您服务】");
        return invoke(methodName, args);
    }
}

使用者

// 使用者
public class Client {
    public static void main(String[] args) {
        ATM atm = new ATM(Bank.class);
        Object takeResult = atm.atmProcess("takeMoney", "uid123456");
        System.out.println(takeResult);

        /*
        Object o = atm.atmProcess("putMoney", 600); // 结果是null,因为返回值void
        System.out.println(o);
        */
    }
}

如果这个时候,功能需要新增一个,那就是存钱,putMoney( Integet num);我们只需要改bank和subject就可以了(上面代码中注释掉的地方),我们的ATM完全不需要改。

诚然,这样实现还有非常多的问题,但是,我们可以大致看到这样的:利用 Java 反射机制,我们可以通过方法名和参数类型来动态调用对象的方法。这种方式使得我们能够在运行时操作对象的方法,而不需要事先知道具体的实现类或方法细节。

动态代理的优缺点总结:

优点:

  • 减少代码重复
  • 运行时灵活性
  • 通用性

缺点:

  • 性能开销:动态代理需要使用反射机制来创建代理类和调用方法,相比静态代理会有性能损失。

②jdk动态代理

JDK 动态代理是一种 运行时代理 技术,它允许你在程序运行时创建代理对象,而不需要提前编写代理类。与传统的静态代理不同,JDK 动态代理是通过反射机制在运行时生成代理类,并且代理类和真实类实现相同的接口。

看好了啊,是==【运行时生成代理类】==,是生成的代理类。与上个例子是不一样的。

他的核心思想:通过 Proxy.newProxyInstance 方法创建一个代理对象,该代理对象会拦截对目标对象方法的调用,然后通过 InvocationHandler 接口的 invoke 方法处理具体的业务逻辑。

1)JDK 动态代理的基本原理
  1. 目标接口(Subject):被代理的类必须实现一个接口。JDK 动态代理 只支持接口代理,如果类没有实现接口,JDK 动态代理就无法使用。这个接口通常定义了业务方法。
  2. InvocationHandler 接口:该接口定义了一个方法 invoke(Object proxy, Method method, Object[] args),它会在每次调用代理对象的方法时被执行。你可以在这个方法内执行一些额外的逻辑【逻辑增强】,如日志记录、权限检查、事务处理等。
  3. Proxy.newProxyInstance:这个方法是 JDK 动态代理的关键,通过它来创建代理对象。它接收三个参数:
    • 类加载器:代理对象的类加载器,通常传入目标对象的类加载器。
    • 接口列表:代理类实现的接口。可以是多个接口。
    • InvocationHandler 实现:代理对象的行为处理器,定义了具体的操作。

我们还是拿银行atm机来举例子

  1. 定义接口: 操作接口
public interface Subject {
    String takeMoney(String uid); // 取钱的功能
    void putMoney(Integer num); // 存钱的功能
}
  1. 实现真实对象:Bank
public class Bank implements Subject {
    @Override
    public String takeMoney(String uid) {
        System.out.println("【银行---用户】: " + uid);
        return "【银行】用户" + uid + "取走" + 600 + "元!";
    }
    @Override
    public void putMoney(Integer num) {
        System.out.println("【银行---存入】: " + num + " 元");
    }
}
  1. 创建 InvocationHandler:定义代理行为( ATM做的事儿 )
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ATM implements InvocationHandler {
    private Object realSubject;
    public ATM(Object realSubject) {
        this.realSubject = realSubject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用目标方法前记录日志
        System.out.println("【proxy】运行前: " + method.getName());
        // 调用真实对象的方法
        Object result = method.invoke(realSubject, args);
        // 在调用目标方法后记录日志
        System.out.println("【proxy】运行后: " + method.getName());
        return result;
    }
}
  1. 创建代理对象、用户使用:通过 Proxy.newProxyInstance 创建代理
import java.lang.reflect.Proxy;
public class ATMClient {
    public static void main(String[] args) {
        // 创建真实对象            【realObject】
        Bank realBank = new Bank();
        // 创建 InvocationHandler  【代理逻辑】
        ATM atm = new ATM(realBank);
        // 创建代理对象               【proxy】
        Subject proxyBank = (Subject) Proxy.newProxyInstance(
            realBank.getClass().getClassLoader(),// 类加载器
            realBank.getClass().getInterfaces(),// 实现的接口
            atm// InvocationHandler
        );
        // 使用代理对象执行方法        【use】
        proxyBank.takeMoney("userTXF");  // 调用代理的 takeMoney 方法
        System.out.println("=====================");
        proxyBank.putMoney(1000);  // 调用代理的 putMoney 方法
        System.out.println(proxyBank.getClass());
    }
}

在这里插入图片描述

会发现,很奇怪???!!这是啥,我这个代码里面没有写过啊??!

都说了,是==【运行时生成代理类】==。代理类实例在程序运行时,由JVM根据反射机制动态的生成。也就是说代理类不是用户自己定义的,而是由JVM生成的。然后发现打包的target目录里面也没有。JVM默认不创建该.class文件,需要增加一个启动参数:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true。

可以在client类的main第一行加这个。System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");,然后运行,就默认在项目的根目录下生成一个com目录,然后就可以发现生成的class文件了。

为啥加了这个参数就可以出现这个文件了呢? 我们分析一下Proxy的一些源码可知其端倪。

// Proxy.java
public class Proxy implements java.io.Serializable {
    private static final long serialVersionUID = -2222568056686623797L;
    // 他的静态内部类
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        private static final String proxyClassNamePrefix = "$Proxy"; // 解释了为什么生成的代理类名字是这个了
        ....
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            ......
            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement(); // 后面的数字
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            //Generate the specified proxy class. 可以看到generateProxyClass有这个方法,名字很形象“生成代理类class”
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            ....
        }
    }
}

//ProxyGenerator.class
public class ProxyGenerator {
    ......
    private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
    ......
    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        .......
        if (saveGeneratedFiles) { // 就是根据这个变量来决定的
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    .....
                    if (var1 > 0) {
                        Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                        Files.createDirectories(var3);
                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                    } else {
                        var2 = Paths.get(var0 + ".class");
                    }
                   ...........
    }
}
2)Proxy

生成的**$Proxy0.class(我这里是0)**是什么样子的?通过上一节生成的来查看一下

在这里插入图片描述

public final class $Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m4;
    private static Method m3;
    private static Method m2;
    private static Method m0; // 代理类代理的五个方法,为什么是五个?因为把equals,toString,hashCode也代理了

    public $Proxy0(InvocationHandler var1) throws  {super(var1);}

    public final boolean equals(Object var1) throws  {
        ...
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        ...
    }
    public final void putMoney(Integer var1) throws  {
        ...
            super.h.invoke(this, m4, new Object[]{var1});
        ...
    }
    public final String takeMoney(String var1) throws  {
       ...
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        ...
    }
    public final String toString() throws  {
        ...
            return (String)super.h.invoke(this, m2, (Object[])null);
        ...
    }
    public final int hashCode() throws  {
       ...
       return (Integer)super.h.invoke(this, m0, (Object[])null);
       ...
    }
    // 静态代码块,最先执行
    static {
       ...
            // 利用反射获取方法字段
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("com.feng.proxy.jdk.Subject").getMethod("putMoney", Class.forName("java.lang.Integer"));
            m3 = Class.forName("com.feng.proxy.jdk.Subject").getMethod("takeMoney", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        ...
    }
}

可以看到生成的这个代理类,里面五个方法的具体实现,都是super.h.invoke(xxx); 我们继续跟进去看一下

public class Proxy implements java.io.Serializable {
    ...
    protected InvocationHandler h; //持有实例,调用的是实现了InvocationHandler的类的invoke方法
    ...
    // 他的静态方法
    public static Object newProxyInstance(ClassLoader loader, //代理对象的类加载器
                                          Class<?>[] interfaces, //代理类实现的接口。可以是多个接口。
                                          InvocationHandler h)  // 自定义实现InvocationHandler的类对象
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
		....
        Class<?> cl = getProxyClass0(loader, intfs);
        try {
            ....
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            ...
            return cons.newInstance(new Object[]{h}); // 返回--这里就没有深究了。。。
        } ...
    }
}

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

这也就解释了“为什么jdk动态代理的类必须要实现接口”?

发现 Proxy这个类只是保存了动态代理的处理器 InvocationHandlerInvocationHandler是接口呀。。。
   
/*
原因是JDK动态代理是基于接口实现的。
当你使用Proxy类创建代理对象时,你需要指定一个接口列表来表示代理对象所应该实现的接口,这些接口就成为代理对象的类型。
代理对象的方法调用会被转发到实现InvocationHandler接口的类中的invoke()方法。这个invoke()方法接受三个参数:代理对象本身、被调用的方法对象和方法的参数数组。invoke()方法需要返回被代理方法调用的结果。
由于代理对象的类型是由接口列表决定的,因此只有实现了接口的类才能被代理。如果你想代理一个类而不是一个接口,你需要使用其他的代理技术,比如CGLIB。
原文链接:https://blog.csdn.net/weixin_58093525/article/details/131737232
    


引用一下 B站Mic 【Java面试】JDK动态代理为什么只能代理有接口的类?【https://www.bilibili.com/video/BV19XtPecE6A/?spm_id_from=333.337.search-card.all.click&vd_source=9ae5b46de4ed16e5ec825e9e94655531】

1.动态代理本身的使用场景或者需求,只是对原始实现的一个拦截,然后去做一些功能的增强或者扩展。而实际的开发模式也都是基于面向接口来开发,所以基于接口来实现动态代理,从需求和场景都是吻合的。当然确实可能存在有些类没有实现接口的,那这个时候,JDK动态代理确实无法满足。

2.在 Java里面,类的继承关系的设计,更多的是考虑到共性能力的抽象,从而提高代码的重用性和扩展性,而动态代理也是在做这样一个事情,它封装了动态代理类生成的抽象逻辑、判断一个类是否是动态代理类、InvocationHandler的持有等等,那么把这些抽象的公共逻辑放在 Proxy这个分类里面,很显然是一个比较正常的设计思路。

总的来说,我认为这个设计上并没有什么特别值得讨论的地方,因为我认为技术方案的设计是解决特定场景问题的。
如果一定要针对普通类来做动态代理,可以选择 cglib这个组件,它会动态生成一个被代理类的子类,子类重写了父类中所有非 final修饰的方法,在子类中拦截父类的所有方法调用从而实现动态代理。

*/
3)思考

如果RealObject【Bank类】是下面的样子呢?然后用生成的代理类去运行,会是怎样的呢

public class Bank implements Subject {
    @Override
    public String takeMoney(String uid) {
        System.out.println("【银行---用户】: " + uid);
        putMoney(6666); // 方法之间的调用==========================
        return "【银行】用户" + uid + "取走" + 600 + "元!";
    }
    @Override
    public void putMoney(Integer num) {
        System.out.println("【银行---存入】: " + num + " 元");
    }
}

客户端测试代码

public static void main(String[] args) {
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    // 创建真实对象
    Bank realBank = new Bank();
    // 创建 InvocationHandler
    ATM atm = new ATM(realBank);
    // 创建代理对象
    Subject proxyBank = (Subject) Proxy.newProxyInstance(
        realBank.getClass().getClassLoader(),      // 类加载器
        realBank.getClass().getInterfaces(),      // 实现的接口
        atm                            // InvocationHandler
    );
    // 使用代理对象执行方法
    proxyBank.takeMoney("user");
}

/* 运行结果---
【proxy】运行前: takeMoney
【银行---用户】: user
【银行---存入】: 6666 元
【proxy】运行后: takeMoney
*/

会发现,内部的putMoney并没有执行代理增强的逻辑。只有最外层的takeMoney执行了增强逻辑。其实分析其调用过程就非常清晰了。

// 第一步 ---- 生成的$Proxy0代理类中
public final String takeMoney(String var1) throws  {
    try {
        return (String)super.h.invoke(this, m3, new Object[]{var1}); // 然后调用实现类的invoke方法(到第二步)
    } catch (RuntimeException | Error var3) {
        throw var3;
    } catch (Throwable var4) {
        throw new UndeclaredThrowableException(var4);
    }
}

// 第二步 ---- 自定义的InvocationHandler实现类
public class ATM implements InvocationHandler {
    ....
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("【proxy】运行前: " + method.getName()); // 1. 输出:【proxy】运行前: takeMoney
        Object result = method.invoke(realSubject, args); // 执行真实对象的方法(去往第三步)
        System.out.println("【proxy】运行后: " + method.getName()); // 4. 输出:【proxy】运行后: takeMoney
        return result;
    }
}

// 第三步 ---- 真实对象Bank
public class Bank implements Subject {
    @Override
    public String takeMoney(String uid) {
        System.out.println("【银行---用户】: " + uid); // 2. 输出:【银行---用户】: user
        putMoney(6666);		// 调用方法,这里仅仅是真是对象Bank调用的自己的另一个方法!!
        return "【银行】用户" + uid + "取走" + 600 + "元!";
    }
    
    @Override
    public void putMoney(Integer num) {
        System.out.println("【银行---存入】: " + num + " 元"); // 3. 输出: 【银行---存入】: 6666 元
    }
}

嵌套调用的话,内部只是简单的方法调用,并不是执行的代理类里面的方法。

③cglib动态代理

由第②节引出一个问题,如果真实对象没有实现接口怎么办?那就是用cglib动态代理,可以说是对jdk动态代理的有效补充。

使用cglib动态代理需要引入第三方包.

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
</dependency>

cglib原理:动态生成一个真实类的子类,子类重写真实类的所有不是 final 的方法。在子类拦截所有父类方法的调用,织入增强逻辑。

织入??好熟悉的字眼?Spring? Aop? 啊啊啊,头好痛。。。。

cglib底层采用的是ASM字节码生成框架,使用字节码技术生成代理类,比使用 Java 反射效率要高。

1)cglib的使用

通过继承目标类,并重写目标类的所有方法,从而在方法调用时加入额外的逻辑(如日志、事务管理、权限检查等)。CGLIB 通过字节码技术实现动态生成目标类的子类,并且利用 MethodInterceptor 来拦截方法调用。

  1. 仍然是银行的例子
public class Bank { //定义了存款和取款的方法,实现了实际的业务逻辑
    private int money;
    public void putMoney( int count ) {
        System.out.println("【银行】: 存入 " + count + "元");
        money += count;
    }
    public void takMoney( int count ) {
        if ( count > money ) System.out.println("【银行】: 取钱失败,余额不足");
        else {
            System.out.println("【银行】: 取钱 " + count + "元");
            money -= count;
        }
    }
}
  1. 实现 MethodInterceptor 接口
public class ATM implements MethodInterceptor {
    /*
    拦截器 ATM:实现了 MethodInterceptor 接口,在 intercept 方法中增强逻辑。proxy.invokeSuper(obj, args) 用于调用目标类的真实方法。
    */
    @Override
    public Object intercept(Object realObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 记录日志:方法调用前
        System.out.println("【proxy 前-- atm校验卡号~~】" + method.getName());
        // 调用真实方法
        Object result = proxy.invokeSuper(realObj, args);
        // 记录日志:方法调用后
        System.out.println("【proxy 后-- atm吐出银行卡~~】" + method.getName());
        return result;
    }
}
  1. User使用
public class CglibMain {
    public static void main(String[] args) {
        // 创建 CGLIB 的 Enhancer 对象
        /*
        Enhancer 类:是 CGLIB 动态代理的核心类,用于创建代理对象。通过 setSuperclass 设置目标类,setCallback 设置方法拦截器。
        */
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Bank.class);  // 设置目标类
        enhancer.setCallback(new ATM());  // 设置方法拦截器
        // 创建代理对象
        Bank atm = (Bank) enhancer.create();
        
        atm.putMoney(500);
        System.out.println("==============");
        atm.takMoney(350);
    }
}
/*
【proxy 前-- atm校验卡号~~】putMoney
【银行】: 存入 500元
【proxy 后-- atm吐出银行卡~~】putMoney
==============
【proxy 前-- atm校验卡号~~】takMoney
【银行】: 取钱 350元
【proxy 后-- atm吐出银行卡~~】takMoney
*/
2)原理解析

参考自:https://blog.csdn.net/sco5282/article/details/121866799

Bank bank = new Bank();
// 创建代理对象
Bank atm = (Bank) enhancer.create();

System.out.println(bank.getClass());
System.out.println(atm.getClass());

/*
class com.feng.proxy.cglib.Bank
class com.feng.proxy.cglib.Bank$$EnhancerByCGLIB$$21b9b61c  不一样!!!!
*/

从Enhance一步一步往下看。

Enhancer 类是 CGLIB 动态代理的核心类。它用于创建代理类的实例。通过 Enhancer 类,可以设置目标类、回调函数(MethodInterceptor)以及其他配置项。

Enhancer enhancer = new Enhancer(); // 创建一个对象
enhancer.setSuperclass(Bank.class);  // 设置目标类
enhancer.setCallback(new ATM());  // 设置方法拦截器

Enhancer#setSuperclass:
public void setSuperclass(Class superclass) { // 说明cglib还可以支持接口代理的
    if (superclass != null && superclass.isInterface()) { // 如果是接口
        this.setInterfaces(new Class[]{superclass});
    } else if (superclass != null && superclass.equals(class$java$lang$Object == null ? (class$java$lang$Object = class$("java.lang.Object")) : class$java$lang$Object)) {
        this.superclass = null;
    } else { // 这才是设置父类
        this.superclass = superclass;
    }
}

Enhancer#setCallback:
public void setCallback(Callback callback) {
    this.setCallbacks(new Callback[]{callback});
}
public void setCallbacks(Callback[] callbacks) {
    if (callbacks != null && callbacks.length == 0) {
        throw new IllegalArgumentException("Array cannot be empty");
    } else {
        this.callbacks = callbacks; // 就是设置一个字段的属性
    }
}

这个enhancer.create()方法看起来很关键。点进去看看

// Enhancer.class
public class Enhancer extends AbstractClassGenerator {
    public Object create() {
        this.classOnly = false;
        this.argumentTypes = null;
        return this.createHelper(); // 这里
    }
    private Object createHelper() {
       ....
        // 调用父类的create方法
        return super.create(KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter, this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID));
    }
}

//AbstractClassGenerator.class
public abstract class AbstractClassGenerator implements ClassGenerator {
    protected Object create(Object key) {
        ....
        if (gen == null) {
            byte[] b = this.strategy.generate(this); // 发现有generate字样
           ...
        }
    }
}

//DefaultGeneratorStrategy.class
public class DefaultGeneratorStrategy implements GeneratorStrategy {
    public byte[] generate(ClassGenerator cg) throws Exception {
        DebuggingClassWriter cw = this.getClassVisitor();
        /*
        transform这个方法名字好熟悉啊,有没有觉得像nio的FileChannel的transferFrom,难道这里说有异曲同工之妙【这里只是乱联想的啊】
        */
        this.transform(cg).generateClass(cw); // 生成class
        return this.transform(cw.toByteArray()); // 返回字节数组
    }
}

//DebuggingClassWriter.class
public class DebuggingClassWriter extends ClassVisitor {
    public byte[] toByteArray() {
        ....
            try {
                (new File(DebuggingClassWriter.debugLocation + File.separatorChar + dirs)).getParentFile().mkdirs();
                //private static String debugLocation = System.getProperty("cglib.debugLocation");
                File file = new File(new File(DebuggingClassWriter.debugLocation), dirs + ".class");
                OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
....
    }
}

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, System.getProperty("user.dir"));同jdk动态代理部分那样设置一下。就可以看到生成的代理类了。

//动态代理类会重写父类的非 final、private 方法;也会构建自己的方法(cglib 方法)
//cglib 方法的方法体:super.方法名,直接调用父类;
//重写方法:它会调用拦截器中的 intercept() 方法
public class Bank$$EnhancerByCGLIB$$21b9b61c extends Bank implements Factory { // 【区别】继承的Bank类
    ....
    final void CGLIB$putMoney$0(int var1) { // 构建的自己的
        super.putMoney(var1);
    }
    public final void putMoney(int var1) { // 重写的我的
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; // 这不就是在获取方法拦截器吗
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$putMoney$0$Method, new Object[]{new Integer(var1)}, CGLIB$putMoney$0$Proxy);
        } else {
            super.putMoney(var1);
        }
    }

    final void CGLIB$takMoney$1(int var1) {
        super.takMoney(var1);
    }

    public final void takMoney(int var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$takMoney$1$Method, new Object[]{new Integer(var1)}, CGLIB$takMoney$1$Proxy);
        } else {
            super.takMoney(var1);
        }
    }
}

在这里插入图片描述

3)思考

参考自:https://blog.csdn.net/starryninglong/article/details/89737419

最开始分析的时候,Enhancer里面有一段这个

if (superclass != null && superclass.isInterface()) { // 如果是接口
    this.setInterfaces(new Class[]{superclass});
}

我们来代理这个Subject接口

public interface Subject {
    void putMoney( int count );
    void takMoney( int count );
}

public class SubjectInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //由于是代理的接口
        System.out.println("【cglib】代理接口" + method.getName());
        return method.getName();
    }
}

//接口代理
public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Subject.class);
    enhancer.setCallback(new SubjectInterceptor());

    Subject subject = (Subject) enhancer.create();
    subject.putMoney(100);
    subject.takMoney(100);
}
/*
【cglib】代理接口putMoney
【cglib】代理接口takMoney
*/

生成的代理类变成这个样子了

public class Subject$$EnhancerByCGLIB$$1cc519db implements Subject, Factory { // 【区别】实现了Subject接口
    
}

综上可以看出

对接口进行代理的cglib,最后是实现了该接口和Factory接口

对类进行代理的cglib,最后是继承了类并实现了Factory接口

可以看出,cglib动态代理

可以代理没有接口的类:与 JDK 动态代理不同,CGLIB 可以代理没有实现接口的普通类。

性能较好:由于 CGLIB 直接生成字节码并创建代理类,相较于 JDK 动态代理的反射机制,性能更高。

更强的功能:CGLIB 可以拦截目标类的所有方法,包括 privateprotected 方法。

但是

无法代理 final 类和 final 方法:由于 CGLIB 需要通过继承目标类来创建代理类,因此无法代理 final 类和 final 方法。

依赖字节码生成:CGLIB 需要依赖字节码生成技术,相对 JDK 动态代理而言,使用起来更加复杂。

④比较

cglib这么好,又可以代理类,还可以代理接口,以后我们动态代理都用cglib吧!!

虽然 CGLIB 动态代理确实非常强大,可以代理普通类(不需要接口)并且性能优于 JDK 动态代理,但 它并不总是最适合所有情况。在选择 JDK 动态代理和 CGLIB 动态代理时,我们需要根据具体的需求来判断,不能一概而论。

  1. 代理对象是否实现接口
  • JDK 动态代理:只能代理实现了接口的类。如果目标类没有实现接口,JDK 动态代理就无法使用。
  • CGLIB 动态代理:可以代理 任何类,即使目标类没有实现接口。CGLIB 会通过继承目标类生成代理类,因此可以代理普通类。
  1. 性能开销
  • JDK 动态代理:由于 JDK 动态代理是基于反射的,其性能相对较低,特别是在调用频繁的场景中,反射机制带来的开销可能会对性能产生一定影响。
  • CGLIB 动态代理:通过字节码技术生成代理类,相比反射来说,CGLIB 的性能通常会更好,尤其在目标类的方法调用较多时。
  1. 目标类是否为 final
  • JDK 动态代理:不受影响,任何类都可以使用 JDK 动态代理。
  • CGLIB 动态代理:无法代理 final 类或者 final 方法,因为 CGLIB 需要通过继承目标类来生成代理类,而 final 类和方法无法被继承和重写。
使用 JDK 动态代理的情况:
  1. 目标类已经实现了接口,且不需要代理 final 类或 final 方法。
  2. 性能要求不高,或目标方法调用不频繁,JDK 动态代理会更简洁、易于理解和维护。
  3. 多接口的情况:JDK 动态代理更适合处理多个接口的代理场景。
public interface A {void methodA();}
public interface B {void methodB();}

public class MyClass implements A, B {
    @Override
    public void methodA() {
        System.out.println("method A");
    }
    @Override
    public void methodB() {
        System.out.println("method B");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before" + method.getName());
        Object result = method.invoke(target, args);  // 调用目标对象的方法
        System.out.println("After" + method.getName());
        return result;
    }
}

public static void main(String[] args) {
        MyClass myClass = new MyClass();
        // 创建代理对象,代理 A 和 B 接口
        A proxyA = (A) Proxy.newProxyInstance(
                MyClass.class.getClassLoader(),
                new Class[]{A.class, B.class},  // 多个接口
                new MyInvocationHandler(myClass)  // InvocationHandler
        );
        // 代理对象调用方法
        proxyA.methodA();  // 调用 A 接口的方法
    
        // 代理对象调用另一个接口的方法
        B proxyB = (B) Proxy.newProxyInstance(
                MyClass.class.getClassLoader(),
                new Class[]{A.class, B.class},  // 多个接口
                new MyInvocationHandler(myClass)  // InvocationHandler
        );
        proxyB.methodB();  // 调用 B 接口的方法
    }
使用 CGLIB 动态代理的情况:
  1. 目标类没有实现接口,需要代理一个没有接口的类。
  2. 性能要求较高,且目标类的方法调用频繁。CGLIB 的性能通常优于 JDK 动态代理。

http://www.kler.cn/a/421451.html

相关文章:

  • spark-sql配置教程
  • PLC协议
  • Node.js 基础教程
  • 【Nacos01】消息队列与微服务之Nacos 介绍和架构
  • 营业执照 OCR 识别 API 的发展前景
  • 通义灵码走进北京大学创新课堂丨阿里云云原生 10 月产品月报
  • Web开发基础学习——HTML, CSS, JavaScript 的区别和联系
  • Java 整合图片处理相关一套:传输、保存、重命名、删除、AI图片文字识别、图片映射、vue3裁剪、设置黑白色、设置负片、提高照片品质
  • 【版本控制】SVN安装到使用一条路讲解
  • 全球【风电叶片专用环氧树脂】市场集中度较高,环氧树脂主要产地在中国、欧洲和美国
  • PyTorch 2.5.1: Bugs修复版发布
  • 论文阅读——量子退火Experimental signature of programmable quantum annealing
  • 常见的数据结构---队列、树与堆的深入剖析
  • 宝塔 8888端口访问被拒绝 腾讯云
  • 【layui】tabs 控件内通过 iframe 加载url 方式渲染tab页面
  • 指针(上)
  • redis签到命令练习
  • Linux学习笔记11 系统启动初始化,服务和进程管理(下)
  • vue3-Import declaration conflicts with local declaration of dayjs
  • SQL面试题——腾讯SQL面试题 连续5天涨幅超过5%的股票
  • mysql的索引在什么情况下会失效?
  • Idea 2024.3 突然出现点击run 运行没有反应,且没有任何提示。
  • 【数据事务】.NET开源 ORM 框架 SqlSugar 系列
  • openEuler 22.03 使用cephadm安装部署ceph集群
  • Go快速入门
  • JAVA设计模式,动态代理模式