0

0

Java如何实现JDK动态代理

WBOY

WBOY

发布时间:2023-04-30 08:49:06

|

1227人浏览过

|

来源于亿速云

转载

概念

代理:为控制a对象,而创建出新b对象,由b对象代替执行a对象所有操作,称之为代理。一个代理体系建立涉及到3个参与角色:真实对象(a),代理对象(b),客户端。

其中的代理对象(B)起到中介作用,连通真实对象(A)与客户端,如果进一步拓展,代理对象可以实现更加复杂逻辑,比如对真实对象进行访问控制。

案例

需求:员工业务层接口调用save需要admin权限,调用list不需要权限,没权限调用时抛出异常提示。

静态代理

/**
 * 代理接口
 */
public interface IEmployeeService {
    void save();
 
    void list();
}
/**
 * 真实对象
 */
public class EmployeeServiceImpl implements IEmployeeService {
    @Override
    public void save() {
        System.out.println("EmployeeServiceImpl-正常的save....");
    }
    @Override
    public void list() {
        System.out.println("EmployeeServiceImpl-正常的list....");
    }
}
/**
 * 模拟当前登录用户对象
 */
public class SessionHolder {
    private static String currentUser;
    public static String  getCurrentUser(){
        return currentUser;
    }
    public static void   setCurrentUser(String currentUser){
        SessionHolder.currentUser = currentUser;
    }
}
/**
 * 代理对象
 */
public class EmployeeProxy implements IEmployeeService {
    //真实对象
    private EmployeeServiceImpl employeeService;
    public EmployeeProxy(EmployeeServiceImpl employeeService){
        this.employeeService = employeeService;
    }
    @Override
    public void save() {
        //权限判断
        if("admin".equals(SessionHolder.getCurrentUser())){
            employeeService.save();
        }else{
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
    }
    @Override
    public void list() {
        employeeService.list();
    }
}
public class App {
    public static void main(String[] args) {
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");  //设置权限(当前登录用户)
        EmployeeProxy employeeProxy = new EmployeeProxy(employeeService);
        employeeProxy.list();
        employeeProxy.save();
    }
}
----------------真实对象--------------------
EmployeeServiceImpl-正常的list....
EmployeeServiceImpl-正常的save....
----------------代理对象--------------------
EmployeeServiceImpl-正常的list....
Exception in thread "main" java.lang.RuntimeException: 当前非admin用户,不能执行save操作
	at com.langfeiyes.pattern.proxy.demo.EmployeeProxy.save(EmployeeProxy.java:20)
	at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)

使用真实对象EmployeeServiceImpl 直接调用时,不管是list 还是save都能直接访问,但不符合需求上的admin权限限制。如果使用代理对象EmployeeProxy,可以完成需求实现。

通过直接创建新类新类代理对象方式完成代理逻辑,这种方式称之为静态代理模式。

立即学习Java免费学习笔记(深入)”;

JDK动态代理模式

Java常用的动态代理模式有JDK动态代理,也有cglib动态代理,此处重点讲解JDK的动态代理

还是原来的需求,前面的IEmployeeService EmployeeServiceImpl SessionHolder 都没变,新加一个JDK代理控制器-EmployeeInvocationHandler

/**
 * jdk动态代理控制类,由它牵头代理类获取,代理方法的执行
 */
public class EmployeeInvocationHandler  implements InvocationHandler {
    //真实对象-EmployeeServiceImpl
    private Object target;
    public EmployeeInvocationHandler(Object target){
        this.target = target;
    }
    //获取jvm在内存中生成代理对象
    public Object getProxy(){
        return  Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
    //代理对象控制执行方法
    //参数1:代理对象
    //参数2:真实对象的方法(使用方式得到方法对象)
    //参数3:真实对象方法参数列表
    //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
        return method.invoke(target, args);
    }
}

测试App类稍微改动下:

public class App {
    public static void main(String[] args) {
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
 
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");
        EmployeeInvocationHandler handler = 
            new EmployeeInvocationHandler(employeeService);
        IEmployeeService proxy = (IEmployeeService) handler.getProxy();
        proxy.list();
        proxy.save();
 
    }
}

上面代码一样可以实现需求,跟静态代理区别就在于少创建了代理对象。此时存在疑问点,没有创建代理对象,为啥可以实现代理类调用呢??

原理分析

先抛出结论JDK动态代理底层实现原理:使用接口实现方式,运行时,在内存中动态构建出一个类,然后编译,执行。这个类是一次性的,JVM停止,代理类就消失。

参与角色 要理解JDK动态代理原理,首先得了解JDK动态代理涉及到的类

Java如何实现JDK动态代理

InvocationHandler:真实对象方法调用处理器,内置invoke方法,其功能:为真实对象定制代理逻辑

EmployeeInvocationHandler:员工服务真实对象方法调用处理器,此类有3个用途: 1>设置真实对象

     //真实对象-EmployeeServiceImpl
    private Object target;
    public EmployeeInvocationHandler(Object target){
        this.target = target;
    }

2>定制代理方法实现逻辑

为真实对象save方法添加了权限校验逻辑

    //代理对象控制执行方法
    //参数1:代理对象
    //参数2:真实对象的方法(使用方式得到方法对象)
    //参数3:真实对象方法参数列表
    //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
        return method.invoke(target, args);
    }

3>返回代理对象

方法执行完之后,返回一个名为:$ProxyX的代理类(其中的X是序号,一般默认为0),这代理类由JDK动态构建出来。

千音漫语
千音漫语

全能AI配音神器

下载
    //获取jvm在内存中生成代理对象
    public Object getProxy(){
        return  Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

Proxy:动态代理控制类,是JDK动态生成的$ProxyX类的父类,它作用如下:
1>通过调用ProxyBuilder 类builder方法构建代理对象类

private static Constructor getProxyConstructor(Class caller,
                                                      ClassLoader loader,
                                                      Class... interfaces){
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
}

2>通过newProxyInstance方法返回$ProxyX类的实例

   public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h) {
    //...
   }

$Proxy0:App类运行时,JDK动态构建出来的代理类,继承至Proxy类

public class App {
    public static void main(String[] args) {
        //System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");
        EmployeeInvocationHandler handler = 
                     new EmployeeInvocationHandler(employeeService);
        IEmployeeService proxy = (IEmployeeService) handler.getProxy();
        proxy.list();
        proxy.save();
 
    }
}

默认情况下JVM是不保存动态创建代理类字节码对象的,可以在main方法中配置代理参数让字节码保留

//JDK8之前
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK8之后
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

执行完之后,会在项目根目录生成代理类字节码对象。 

Java如何实现JDK动态代理

为了方便解读,将一些不需要的方法剔除之后

$Proxy0类

public class $Proxy0 extends Proxy implements IEmployeeService {
    private static Method m4;
    private static Method m3;
    static {
        try {
            m4 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
                 .getMethod("save");
            m3 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
                 .getMethod("list");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public $Proxy0(InvocationHandler var1) throws Throwable {
        super(var1);
    }
    public final void save() throws Throwable {
        super.h.invoke(this, m4, (Object[])null);
    }
 
    public final void list() throws  Throwable{
        super.h.invoke(this, m3, (Object[])null);
    }
}

从源码上看,$Proxy0的特点:

  • 1>继承了Proxy类,实现了IEmployeeService 接口

  • 2>通过静态块的方式反射IEmployeeService接口save与list方法,得到他们的方法对象Method

  • 3>调用父类构造器,需要传入InvocationHandler 参数

  • 4>重写IEmployeeService接口的save list方法靠的是父类Proxy的h属性.invoke方法

真相大白

下图所有参与动态代理的类:

Java如何实现JDK动态代理

 下图是上图的操作时序图,跟着走就对了

Java如何实现JDK动态代理

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

通义千问
通义千问

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

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1132

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

213

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1773

2025.12.29

java接口相关教程
java接口相关教程

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

20

2026.01.19

java入门学习合集
java入门学习合集

本专题整合了java入门学习指南、初学者项目实战、入门到精通等等内容,阅读专题下面的文章了解更多详细学习方法。

2

2026.01.29

java配置环境变量教程合集
java配置环境变量教程合集

本专题整合了java配置环境变量设置、步骤、安装jdk、避免冲突等等相关内容,阅读专题下面的文章了解更多详细操作。

2

2026.01.29

java成品学习网站推荐大全
java成品学习网站推荐大全

本专题整合了java成品网站、在线成品网站源码、源码入口等等相关内容,阅读专题下面的文章了解更多详细推荐内容。

0

2026.01.29

Java字符串处理使用教程合集
Java字符串处理使用教程合集

本专题整合了Java字符串截取、处理、使用、实战等等教程内容,阅读专题下面的文章了解详细操作教程。

0

2026.01.29

Java空对象相关教程合集
Java空对象相关教程合集

本专题整合了Java空对象相关教程,阅读专题下面的文章了解更多详细内容。

3

2026.01.29

热门下载

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

精品课程

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

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.9万人学习

Java 教程
Java 教程

共578课时 | 52.9万人学习

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

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