JDK动态代理和CGLIB动态代理

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Java动态代理允许在运行时创建代理对象,增强或拦截目标类的方法调用,无需修改原代码。它有两种主要实现方式:JDK动态代理和CGLIB动态代理。- **JDK动态代理**:通过`java.lang.reflect.Proxy`类和`InvocationHandler`接口实现,适用于实现了接口的类。它在方法调用前后插入额外逻辑,如日志记录、权限控制等。 - **CGLIB动态代理**:基于字节码技术,为未实现接口的类生成子类作为代理,重写父类方法以添加增强逻辑。适用于没有接口的类,但要求目标类不能是`final`类或方法。

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://juejin.cn/post/7374343669208612873

相关文章
|
1月前
|
Java Spring
JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: ● JDK动态代理只提供接口的代理,不支持类的代理Proxy.newProxyInstance(类加载器, 代理对象实现的所有接口, 代理执行器) ● CGLIB是通过继承的方式做的动态代理 , 如果某个类被标记为final,那么它是无法使用 CGLIB做动态代理的。Enhancer.create(父类的字节码对象, 代理执行器)
|
1月前
|
监控 Java API
JDK动态代理和CGLIB动态代理
Java动态代理允许在运行时创建代理对象,增强或拦截目标类方法的执行。主要通过两种方式实现:JDK动态代理和CGLIB动态代理。JDK动态代理基于接口,利用`java.lang.reflect.Proxy`类和`InvocationHandler`接口;CGLIB则通过字节码技术生成目标类的子类作为代理,适用于未实现接口的类。两者均用于在方法执行前后添加额外逻辑,如日志记录、权限控制等,广泛应用于AOP框架中。
|
2月前
|
Java API 数据安全/隐私保护
探索Java动态代理的奥秘:JDK vs CGLIB
动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
75 0
探索Java动态代理的奥秘:JDK vs CGLIB
|
5月前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
374 5
|
6月前
|
Java Spring 数据库连接
[Java]代理模式
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
111 0
[Java]代理模式
|
6月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
72 1
|
7月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
662 61
|
1月前
|
Java Linux 应用服务中间件
在Rocky Linux 9上安装JDK并配置环境变量!
本教程介绍在Rocky Linux 9上安装JDK并配置环境变量的完整步骤。首先更新系统,清理旧版本JDK相关包及残留文件,确保环境干净。接着搜索并安装所需版本的JDK(如OpenJDK 17),验证安装是否成功。然后查找JDK安装路径,配置全局环境变量`JAVA_HOME`和`PATH`,最后验证环境变量设置。按照此流程操作,可顺利完成Java开发环境搭建,支持多版本切换(如JDK 8/11/17)。生产环境请谨慎操作,避免影响现有服务。
145 21
|
1月前
|
Oracle Java 关系型数据库
课时4:JDK的安装与配置
课时4:JDK的安装与配置 摘要: 1. JDK安装:从Oracle官网下载适合操作系统的JDK版本,确保关闭防火墙,选择正确的位数(如64位),并进行一键式安装。 2. JDK配置:将JDK的bin目录路径(如D:\Java\jdk1.8.0_74\bin)添加到系统环境变量PATH中,确保Java开发命令(如javac、java)可用。配置完成后,重启命令行工具验证安装是否成功。 通过以上步骤,确保Java开发环境的正确搭建。
154 0
|
1月前
|
Java
课时5:JDK安装与配置
课时5:JDK安装与配置,主讲人李兴华。课程详细讲解了JDK的安装步骤和环境配置方法,包括选择安装路径、配置系统环境变量(如path),确保javac和java命令在命令行中可用。建议将所有程序安装在D盘,便于管理。安装完成后,需重启命令行以加载新环境配置,确保Java开发环境正常运行。