0

0

怎么实现动态代理?

煙雲

煙雲

发布时间:2025-11-25 09:19:27

|

644人浏览过

|

来源于php中文网

原创

动态代理是一种在运行时生成代理对象的技术,用于在不修改目标对象的前提下增强其功能。它通过jdk动态代理(基于接口)或cglib(基于继承)实现,前者要求目标类实现接口,后者可代理普通类但无法处理final类或方法。核心价值在于解耦横切关注点,如日志、事务、权限控制等,广泛应用于spring aop、rpc框架、缓存、性能监控等场景。jdk代理依赖反射,cglib通过生成子类实现,各有适用场景:优先使用jdk代理以符合接口编程,无接口时选用cglib。尽管带来调试复杂、性能开销、维护成本等挑战,但合理使用可显著提升代码可维护性与扩展性。

怎么实现动态代理?

动态代理,简单来说,它就是一个在运行时动态生成的“替身”或者说“中介”。这个替身能代表你的真实对象去执行操作,更妙的是,它还能在不修改真实对象代码的前提下,在操作前后偷偷地加点自己的逻辑,比如记录日志、权限校验或者事务管理什么的。它的核心价值在于,让你能够优雅地解耦横切关注点,让你的核心业务逻辑保持纯粹。

怎么实现动态代理?

实现动态代理,在Java生态里,我们通常会用到两种主流方式:JDK自带的动态代理机制,以及第三方库CGLIB。

1. 基于JDK的动态代理

这是Java标准库提供的能力,它要求你的目标对象必须实现一个或多个接口。原理是,JDK会根据你提供的接口,在运行时生成一个实现了这些接口的代理类,并创建这个代理类的实例。所有对接口方法的调用,都会被转发到一个InvocationHandler接口的实现类中。

具体步骤是这样的:

  • 定义接口: 你的业务逻辑必须通过接口来暴露。
  • 实现InvocationHandler 这个是核心。你需要创建一个类,实现java.lang.reflect.InvocationHandler接口,并重写它的invoke方法。invoke方法会在代理对象的方法被调用时触发,你可以在这里面加入前置、后置逻辑,然后通过method.invoke(target, args)来调用真实对象的方法。
  • 创建代理实例: 使用java.lang.reflect.Proxy.newProxyInstance()静态方法来创建代理对象。你需要传入类加载器、目标对象实现的接口数组,以及你的InvocationHandler实例。

来个小例子:

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

// 1. 定义接口
interface Service {
    void doSomething();
    String getData(String id);
}

// 2. 真实的服务实现
class ServiceImpl implements Service {
    @Override
    public void doSomething() {
        System.out.println("ServiceImpl: Doing something important.");
    }

    @Override
    public String getData(String id) {
        System.out.println("ServiceImpl: Getting data for ID: " + id);
        return "Data for " + id;
    }
}

// 3. 实现InvocationHandler
class LogInvocationHandler implements InvocationHandler {
    private Object target; // 真实的目标对象

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("--- 代理前置日志: 调用方法 " + method.getName());
        if (args != null) {
            for (Object arg : args) {
                System.out.println("--- 参数: " + arg);
            }
        }

        // 调用真实对象的方法
        Object result = method.invoke(target, args);

        System.out.println("--- 代理后置日志: 方法 " + method.getName() + " 执行完毕");
        if (result != null) {
            System.out.println("--- 返回值: " + result);
        }
        return result;
    }
}

// 4. 使用示例
public class JdkProxyDemo {
    public static void main(String[] args) {
        ServiceImpl target = new ServiceImpl();

        // 创建代理对象
        Service proxy = (Service) Proxy.newProxyInstance(
            target.getClass().getClassLoader(), // 类加载器
            target.getClass().getInterfaces(),   // 目标对象实现的接口
            new LogInvocationHandler(target)     // InvocationHandler实例
        );

        System.out.println("--- 调用代理对象的doSomething方法 ---");
        proxy.doSomething();
        System.out.println("\n--- 调用代理对象的getData方法 ---");
        String data = proxy.getData("123");
        System.out.println("最终获取到的数据: " + data);
    }
}

2. 基于CGLIB的动态代理

当你的目标对象没有实现任何接口时,JDK动态代理就无能为力了。这时候,CGLIB(Code Generation Library)就派上用场了。CGLIB通过继承目标类的方式来创建代理,所以它能代理普通的类,但不能代理final类或者final方法(因为final的不能被继承或重写)。

CGLIB的核心是EnhancerMethodInterceptor

  • 实现MethodInterceptor 类似于JDK的InvocationHandler,你需要实现net.sf.cglib.proxy.MethodInterceptor接口,并重写intercept方法。
  • 创建代理实例: 使用net.sf.cglib.proxy.Enhancer来设置父类(即你的目标类)和回调函数(你的MethodInterceptor实现),然后调用create()方法生成代理对象。

CGLIB的例子会稍微复杂一点,但核心思想和JDK代理是类似的,都是在方法调用前后插入逻辑。

为什么我们需要动态代理?它的核心价值在哪里?

我个人觉得,动态代理这玩意儿,它的魅力在于一种“润物细无声”的扩展能力。它不像直接修改源代码那样,会污染你的核心业务逻辑,而是像一个透明的“过滤器”或者“增强器”,在不侵入原有代码的情况下,给你的方法调用加点料。

纯css3实现天空云层动态特效
纯css3实现天空云层动态特效

云朵视差特效代码,纯css3实现的天空云层动态特效

下载

它的核心价值主要体现在以下几个方面:

  • 横切关注点(Cross-Cutting Concerns)的解耦: 这是动态代理最经典的用例。像日志记录、事务管理、权限校验、性能监控、缓存等,这些功能往往会散落在多个业务方法中,如果直接写在业务代码里,会让业务逻辑变得臃肿且难以维护。动态代理可以将这些“横切”的逻辑集中管理,通过代理在方法执行前后自动织入,极大地提高了代码的内聚性和可维护性。你想想,如果每个业务方法都要手动写一遍日志,那得多累,而且一旦日志格式变了,改起来简直是灾难。
  • 运行时增强与灵活性: 代理是在运行时生成的,这意味着你可以在不重新编译甚至不重启应用的情况下,动态地改变或增强对象的行为。这为一些高级框架(比如Spring AOP)提供了强大的运行时可配置性。
  • 框架的基础: 很多流行的Java框架都离不开动态代理。比如:
    • Spring AOP: 它是Spring框架实现面向切面编程的核心技术,底层就是基于JDK动态代理或CGLIB来实现切面的织入。
    • RPC(远程过程调用)框架: 像Dubbo这样的RPC框架,客户端调用远程服务时,通常会创建一个服务的本地代理对象。你调用的其实是这个代理,代理负责把请求序列化、网络传输,然后把结果反序列化回来。
    • ORM框架: 比如Hibernate,它在进行懒加载(Lazy Loading)时,可能会为关联对象生成代理,只有当你真正访问这个关联对象时,代理才会去数据库加载数据,避免了一次性加载过多数据造成的性能问题。
    • Mocking框架: JUnit测试中常用的Mockito、EasyMock等,它们创建模拟对象(Mock Object)来隔离测试依赖,也大量使用了动态代理技术。

所以,动态代理不仅仅是一种技术实现,它更是一种设计思想,一种让你能够更优雅、更灵活地构建和扩展软件系统的利器。

Java的动态代理(JDK Proxy)与CGLIB代理有什么区别和适用场景?

关于JDK动态代理和CGLIB,我发现很多人初学时会纠结到底用哪个,或者觉得CGLIB更“高级”。但实际上,它们各有侧重,并非孰优孰劣,而是适用场景不同。

1. 核心原理上的差异:

  • JDK动态代理: 基于接口。它通过反射机制,在运行时为目标对象实现的接口生成一个新的代理类,这个代理类实现了与目标对象相同的接口。当调用代理对象的方法时,实际上是调用了InvocationHandlerinvoke方法。
  • CGLIB动态代理: 基于继承。它通过修改字节码技术,在运行时生成目标类的子类。因为是子类,所以它可以覆盖父类(即目标类)的方法,从而在方法调用前后插入增强逻辑。

2. 适用场景上的差异:

  • JDK动态代理的适用场景:
    • 目标对象实现了接口。 这是它的前提条件。如果你的设计中,业务逻辑都通过接口来抽象,那么JDK代理是首选,因为它更符合面向接口编程的范式,也更轻量级。
    • 对性能要求不是极端苛刻。 虽然涉及到反射,但现代JVM对JDK代理的优化已经很好了,对于绝大多数应用来说,性能开销可以忽略不计。
    • 代码结构更清晰。 由于是基于接口的,你很容易就能看出哪些方法是可代理的,哪些是核心业务逻辑。
  • CGLIB动态代理的适用场景:
    • 目标对象没有实现接口。 这是CGLIB存在的最大理由。当你需要代理一个普通的类,或者一个第三方库中你无法修改其源码的类时,CGLIB就成了唯一的选择。
    • 需要代理final以外的方法。 CGLIB通过继承实现,所以不能代理final修饰的类和方法。
    • 对性能有微小要求。 在某些极端场景下,CGLIB可能比JDK代理有略微的性能优势,因为它生成的是字节码,通常比反射调用更快一点。但这个差异在日常应用中很难感知。

3. 限制与注意事项:

  • JDK动态代理的限制: 只能代理接口。如果你尝试代理一个没有实现任何接口的类,会直接报错。
  • CGLIB动态代理的限制:
    • 不能代理final类或final方法。因为final的不能被继承或重写。
    • 构造函数:CGLIB创建代理对象时,会调用目标类的无参构造函数。如果目标类只有有参构造函数,或者无参构造函数是私有的,那么CGLIB可能会遇到问题。
    • 需要引入第三方库依赖。

我个人的经验是,如果能用JDK代理,我通常会优先考虑它,因为它更“原生”,依赖更少,而且基于接口的设计也更符合软件工程的最佳实践。只有当明确目标对象没有接口,或者有其他特殊需求时,才会转向CGLIB。很多框架(如Spring)会智能地根据目标对象是否有接口来自动选择使用哪种代理方式。

在实际开发中,动态代理有哪些常见的应用场景和潜在的挑战?

动态代理在实际开发中简直是无处不在,尤其是在各种框架的底层实现中。但它也并非万能药,使用不当也会带来一些挑战。

常见的应用场景:

  • 事务管理(Transaction Management): 这是最常见的场景之一。在一个方法执行前开启事务,方法执行成功后提交事务,失败则回滚。通过动态代理,你可以在不修改业务代码的情况下,为任何需要事务支持的方法添加事务切面。例如,Spring的@Transactional注解底层就是通过动态代理实现的。
  • 日志记录(Logging): 可以在方法执行前记录请求参数,方法执行后记录返回结果和执行时间。这对于排查问题、监控系统行为非常有用。
  • 权限控制(Security/Authorization): 在方法执行前检查当前用户是否有权限访问该方法。如果没有,则直接抛出异常或返回错误信息。
  • 性能监控(Performance Monitoring): 记录方法的执行时间,统计调用次数,帮助你找出系统中的性能瓶颈
  • 缓存(Caching): 在方法执行前检查缓存中是否已有结果,如果有则直接返回;如果没有,则执行方法并将结果存入缓存。这可以显著提高系统响应速度,减少对后端资源的访问。
  • 远程服务调用(RPC): 就像前面提到的,Dubbo等RPC框架会为远程服务生成本地代理,让客户端像调用本地方法一样调用远程服务,底层复杂的网络通信、序列化/反序列化都被代理封装起来了。
  • 数据源切换(Dynamic Data Source Routing): 在多数据源环境中,可以根据方法名、参数或注解等,在方法执行前动态切换到不同的数据源。
  • AOP(Aspect-Oriented Programming)框架: 动态代理是实现AOP的核心技术。它允许你将与业务逻辑无关的横切关注点模块化,并在运行时动态地“织入”到程序中。

潜在的挑战:

  • 调试复杂性: 当你遇到问题时,堆跟踪(Stack Trace)可能会变得很长且复杂,因为方法调用会经过代理层。你看到的调用栈可能不是直接从A到B,而是A -> 代理 -> B,这会给调试带来一定的困扰。我记得有几次调试Spring AOP的问题,看着那长长的代理调用链,确实有点头疼。
  • 性能开销: 尽管现代JVM和CGLIB对动态代理做了很多优化,但毕竟它涉及到反射或字节码生成,相比直接方法调用,还是会存在一定的性能开销。对于每秒数百万次调用的超高并发场景,这可能需要被考虑,但对于大多数业务系统,这种开销通常可以忽略不计。
  • 理解和维护成本: 如果过度使用动态代理,或者代理链过长、逻辑过于复杂,可能会让代码变得难以理解和维护。新人接手项目时,可能会对这些“看不见”的逻辑感到困惑。
  • final关键字的限制: CGLIB不能代理final类和final方法。如果你设计的类或方法被final修饰,那么CGLIB就无法对其进行代理增强。
  • 构造函数问题: CGLIB在创建代理时,需要调用目标类的无参构造函数。如果目标类没有无参构造函数,或者无参构造函数是私有的,CGLIB就无法创建代理实例。

总的来说,动态代理是一个非常强大的工具,它极大地提高了代码的模块化和可扩展性。但就像任何强大的工具一样,它需要被恰当地使用。在选择使用动态代理时,我们应该权衡其带来的好处和潜在的复杂性,确保它能真正解决问题,而不是引入新的问题。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

155

2025.08.06

Java Spring Security 与认证授权
Java Spring Security 与认证授权

本专题系统讲解 Java Spring Security 框架在认证与授权中的应用,涵盖用户身份验证、权限控制、JWT与OAuth2实现、跨站请求伪造(CSRF)防护、会话管理与安全漏洞防范。通过实际项目案例,帮助学习者掌握如何 使用 Spring Security 实现高安全性认证与授权机制,提升 Web 应用的安全性与用户数据保护。

88

2026.01.26

hibernate和mybatis有哪些区别
hibernate和mybatis有哪些区别

hibernate和mybatis的区别:1、实现方式;2、性能;3、对象管理的对比;4、缓存机制。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

158

2024.02.23

Hibernate框架介绍
Hibernate框架介绍

本专题整合了hibernate框架相关内容,阅读专题下面的文章了解更多详细内容。

92

2025.08.06

Java Hibernate框架
Java Hibernate框架

本专题聚焦 Java 主流 ORM 框架 Hibernate 的学习与应用,系统讲解对象关系映射、实体类与表映射、HQL 查询、事务管理、缓存机制与性能优化。通过电商平台、企业管理系统和博客项目等实战案例,帮助学员掌握 Hibernate 在持久层开发中的核心技能。

39

2025.09.02

Hibernate框架搭建
Hibernate框架搭建

本专题整合了Hibernate框架用法,阅读专题下面的文章了解更多详细内容。

72

2025.10.14

软件测试常用工具
软件测试常用工具

软件测试常用工具有Selenium、JUnit、Appium、JMeter、LoadRunner、Postman、TestNG、LoadUI、SoapUI、Cucumber和Robot Framework等等。测试人员可以根据具体的测试需求和技术栈选择适合的工具,提高测试效率和准确性 。

462

2023.10.13

java测试工具有哪些
java测试工具有哪些

java测试工具有JUnit、TestNG、Mockito、Selenium、Apache JMeter和Cucumber。php还给大家带来了java有关的教程,欢迎大家前来学习阅读,希望对大家能有所帮助。

313

2023.10.23

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

4

2026.03.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.3万人学习

C# 教程
C# 教程

共94课时 | 11万人学习

Java 教程
Java 教程

共578课时 | 79.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号