细说java动态代理及使用场景

news2024/11/28 0:53:21

一、定义

Java代理模式是一种结构型设计模式,它允许通过创建一个代理对象来间接访问另一个对象,从而控制对原始对象的访问。

1.1 作用

1、在访问原始对象时增加额外功能,如访问前或访问后添加一些额外的行为。
2、控制对原始对象的访问。

Java代理模式包括两个主要组件:代理类和实际的对象。当客户端调用代理对象的方法时,代理对象会将请求转发到实际对象,并在必要时添加额外的功能。这些额外的功能可以是日志记录、安全性检查、缓存等等。

1.2 分类

Java代理模式可以分为静态代理和动态代理两种类型:

  • 静态代理:需要手动编写代理类,代理对象和原始对象都需要实现相同的接口。
  • 动态代理:使用Java反射机制自动生成代理类,在运行时绑定原始对象和代理对象,无需手动编写代理类,但原始对象必须实现接口。
    Java代理模式在开发中广泛应用,它提供了更高级别的抽象,使得代码更加清晰、易于维护和调试

下面我们分别来介绍这两种代理方式。

二、静态代理

这个比较简单,我们直接上图上代码,这种代理方式在平时开发过程中也不是太多。
在这里插入图片描述
一个接口,两个实现类,一个真实现,一个假实现,代理类持有实现类,通过实现类来做操作。
举例:买股票。
在这里插入图片描述
代码如下

// 定义一个交易接口
interface ITransaction {
    void buy(String stockName, int quantity);
    void sell(String stockName, int quantity);
}


// 实现交易接口的真实交易类
class RealTransaction implements ITransaction {
    @Override
    public void buy(String stockName, int quantity) {
        System.out.println("买入 " + quantity + " 股 " + stockName + " 成功");
    }


    @Override
    public void sell(String stockName, int quantity) {
        System.out.println("卖出 " + quantity + " 股 " + stockName + " 成功");
    }
}


// 交易静态代理类
class TransactionProxy implements ITransaction {
    private ITransaction realTransaction;
    public TransactionProxy(ITransaction realTransaction) {
        this.realTransaction = realTransaction;
    }

    @Override
    public void buy(String stockName, int quantity) {
        // 在真实交易前做一些操作
        System.out.println("交易开始...");
        // 调用真实交易类的买入方法
        realTransaction.buy(stockName, quantity);
        // 在真实交易后做一些操作
        System.out.println("交易结束。");
    }

    @Override
    public void sell(String stockName, int quantity) {
        // 在真实交易前做一些操作
        System.out.println("交易开始...");
        // 调用真实交易类的卖出方法
        realTransaction.sell(stockName, quantity);
        // 在真实交易后做一些操作
        System.out.println("交易结束。");
    }
}

// 测试类
public class TransactionTest {
    public static void main(String[] args) {
        // 创建一个真实交易类对象
        RealTransaction realTransaction = new RealTransaction();
        // 创建交易静态代理类对象,传入真实交易类对象
        TransactionProxy transactionProxy = new TransactionProxy(realTransaction);
        
        // 调用交易静态代理类的买入方法
        transactionProxy.buy("AAPL", 100);
        transactionProxy.sell("AAPL", 100);
     }
}

三、动态代理

动态代理更加灵活,代理类在程序运行时创建。
动态代理在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。如:Spring AOP、retrofit、注解(如wmrouter)等。

我们常见的动态代理使用方式有三种:

  1. 基于 JDK 实现动态代理 (只能代理实现了接口的类)
  2. 基于CGlib 动态代理模式 (基于ASM的字节码生成库)
  3. 基于 Aspectj 实现动态代理

我们飞别来介绍一下

3.1 基于 JDK 实现

即JDK 动态代理机制,JDK动态代理是一种实现在Java中实现AOP编程的方式。它可以在jvm运行时创建一个代理对象(动态地创建某个接口的实例),用于替换原始对象并截取其方法调用。
先上一张图
在这里插入图片描述

3.1.1我们先说下使用步骤

动态代理主要分为以下两个部分:

代理对象的静态生成过程 (将定义的接口以及 InvocationHandler 实例传递给 Proxy.newProxyInstance() 方法)
-> —
对代理对象方法的拦截和重构过程(代理对象会进入 InvocationHandler 的 invoke() 方法,从而可以通过反射机制去调用具体的真实对象)

  • 第一步:实现InvocationHandler接口创建自己的调用处理器
package com.test.daili;

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

/**
 * @param <T>
 */
public class BrokerInvocationHandler<T> implements InvocationHandler {
    /**
     * 持有的被代理对象
      */
    T target;
    public BrokerInvocationHandler(T target) {
        this.target = target;
    }
    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理开始执行" +method.getName() + "方法");
        //代理过程中插入其他操作
        // TODO: 2023/5/11 计算手续费
        Object result = method.invoke(target, args);
        // TODO: 2023/5/11 数据库操作
        return result;
    }
}

  • 第二步:新建一个交易接口及其实现类
public interface ITrade {
    void trade();
}

public class StockMan implements ITrade {
    private String codename;

    private int codenum;
    public StockMan(String codename) {
        this.codename = codename;
    }

    public StockMan(String codename, int codenum) {
        this.codename = codename;
        this.codenum = codenum;
    }

    @Override
    public void trade() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(codename + ", num=" + codenum + " 交易");
    }
}
  • 第三步:使用newInstanceProxy 完成代理对象的创建

public class ProxyTest {
    public static void main(String[] args) {

//        创建一个被代理的实例对象
        ITrade stockMan = new StockMan("中国平安", 1000);

        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new BrokerInvocationHandler<ITrade>(stockMan);

        //创建一个代理对象 ,代理对象的每个执行方法都会替换执行 Invocation中的 invoke方法
        ITrade stockProxy = (ITrade) Proxy.newProxyInstance(StockMan.class.getClassLoader(),
                new Class<?>[]{ITrade.class}, stuHandler);

        //代理执行交易方法
        stockProxy.trade();
    }
}

3.1.2 jdk动态代理原理

JDK动态代理主要是基于反射,使用反射解析目标对象的属性、方法等,根据解析的内容生成proxy.class,大白话就是我们可以在运行时生成一个代理对象,并将所有方法调用转发给我们指定的处理器。
在这里插入图片描述
继续看图,别忘记了
在这里插入图片描述
在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心,其中Proxy用于创建实现了一组给定接口的代理类,InvocationHandler则负责处理代理类的方法调用。

InvocationHandler 接口

public interface InvocationHandler {
   
    proxy - 代理的真实对象
    method - 指的是我们所要调用真实对象的某个方法的Method对象
    args - 指的是调用真实对象某个方法时接受的参数
    public Object invoke(Object realIproxy, Method method, Object[] args)throws Throwable;
}

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个InvocationHandler

Proxy

// 1、判断是否有创建代理类的权限

// 2、获取代理类 class 对象
Class<?> cl = getProxyClass0(loader, intfs);

 // 3、获得代理类构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);

// 4、反射创建实例

// 指定代理对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy);

// 关联于指定类装载器和一组接口的动态代理类的类对象
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces); 

// 判断指定类对象是否是一个动态代理类
public static boolean isProxyClass(Class<?> cl);

真正的代理类对象是com.sun.proxy.$Proxy0,我们定义InvocationHandler类是用于添加对代理类的功能扩展!而
$Proxy0类继承java.lang.reflect.Proxy类 并实现ITrade接口 ,因此它的类声明方式如下:

public class  $Proxy0 extends Proxy  implements ITrade 

具体来看下生成过程

loader :类加载器,用于加载代理对象。
interfaces : 被代理类实现的一些接口;
h : 实现了 InvocationHandler 接口的对象;
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
      //所有被实现的业务接口
      final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();
    
      // 2、通过反射类中的Constructor获取其所有构造方法
      Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
      // 3、用构造方法创建代理类com.sun.proxy.$ProxyX的实例,并传入InvocationHandler参数
      return cons.newInstance(new Object[]{h});
}

/**
 * 核心生成字节码的方法
 */
private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces)
    {
        // optimization for single interface
        if (interfaces.length == 1) {
            ...
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        } else {
            // interfaces cloned
            final Class<?>[] intfsArray = interfaces.clone();
            ...
            return proxyCache.sub(intfs).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        }
    }

在builder方法内部,有这么一句
            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);

为了更清楚的看到调用过程,我们仿ProxyGenerator.generateProxyClass方法的方式生成其class字节码


        //真实对象
        try {
            Class proxyGenerator = Class.forName("java.lang.reflect.ProxyGenerator");

            Method method = proxyGenerator.getDeclaredMethod("generateProxyClass", String.class, Class[].class);
            method.setAccessible(true);

            //生成代理类
            byte[] proxyClassFile = (byte[]) method.invoke(null, "$Proxy0", stockMan.getClass().getInterfaces());

            //保存在本地文件中
            try (FileOutputStream fis = new FileOutputStream(new File("./$Proxy0.class"))){
                fis.write(proxyClassFile);
            } catch (Exception e) {
                e.printStackTrace();
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

生成的代码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import com.test.daili.ITrade;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

/**
 * Proxy会为我们生成一个实现了ITrade接口并继承了Proxy的业务代理类$Proxy0
 * 在进行方法调用时,其实是调用了InvocationHandler的 invoke方法,
 *  如下: super.h.invoke(this, m3, (Object[])null);
 */
public final class $Proxy0 extends Proxy implements ITrade {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

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

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

	/**
	 * 方法调用
	 */
    public final void trade() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.test.daili.ITrade").getMethod("trade");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

3.2 CGLIB 动态代理

CGLIB 动态代理是基于目标对象创建子类的方式实现的,它可以在运行时动态修改目标对象的字节码,从而达到代理的目的,可以代理没有任何接口的类( JDK 动态代理必须要接口)。

相比于静态代理和 JDK 动态代理,CGLIB 的性能更优秀,因为其直接操作字节码,避免了反射机制的调用,使得代理方法的调用速度更快。但是,CGLIB 也有缺点,就是在创建代理类时需要消耗更多的时间和内存。

由于一些原因,这个大家可自行学习:
https://github.com/cglib/cglib

四、使用场景

动态代理的使用场景比较多,常见的各种开源框架如:Spring AOP、retrofit(create() 方法)、注解(如wmrouter)

除了各种框架,我们在项目中也会使用到,如 hook toast报错,webview client hook等

demo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/533220.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

热水智能控制系统有什么优点?

热水智能控制系统是一种先进的技术&#xff0c;可以极大地提高家庭和商业场所的热水使用效率&#xff0c;降低能源消耗和运营成本。这种系统利用现代化的传感器、控制器和通讯技术&#xff0c;可以智能地监测和控制热水的温度、流量和使用情况&#xff0c;并根据实际需求来调节…

ASEMI代理ADI亚德诺ADM706SARZ-REEL原厂芯片

编辑-Z ADM706SARZ-REEL参数描述&#xff1a; 型号&#xff1a;ADM706SARZ-REEL VCC工作电压范围&#xff1a;1.0-5.5V 电源电流&#xff1a;100μA 重置阈值滞后&#xff1a;20 mV 复位脉冲宽度&#xff1a;200 ms PFI输入阈值&#xff1a;1.25V PFI输入电流&#xff…

Linux 部署 scrapydweb

一、 创建虚拟环境&#xff0c;在虚拟环境下操作 1、安装scrapyd pip install scrapyd2、安装scrapyd-client pip install scrapyd-client3、安装scrapydweb pip install scrapydweb4、安装Logparser pip install Logparser二、新建一个scracyd的配置文件 sudo mkdir /etc/scr…

MySql.Data.dll 因版本问题造成报错的处理

NetCore 链接MySQL 报 Character set ‘utf8mb3‘ is not supported by .Net Framework 异常解决_character set utf8mb3_csdn_aspnet的博客-CSDN博客 查看mysql版本号&#xff0c;两种办法&#xff1a; 第一种在数据库中执行查询&#xff1a;SELECT version; 第二种使用工具…

数据治理和合规性:如何确保大数据应用遵守法规和标准

第一章&#xff1a;引言 在数字时代&#xff0c;大数据的应用日益普遍&#xff0c;对企业和组织的决策、运营和创新产生了深远的影响。然而&#xff0c;随着数据规模的不断增长&#xff0c;以及数据泄露和滥用事件的频繁发生&#xff0c;数据治理和合规性问题愈发突显。企业和…

推荐系统用户长序列建模

目录 一、背景 二、技术方案 2.1 DIN 简介 论文细节 优缺点 2.2 DINE 简介 论文细节 2.3 MIMN 简介 论文细节 2.4 SIM 简介 论文细节 优缺点 2.5 DSIN 简介 论文细节 一、背景 阿里巴巴的精排模型从传统lr&#xff0c;到深度学习&#xff0c;再到对用户长历…

使用云服务器可以做什么?十大使用场景举例说明

使用阿里云服务器可以做什么&#xff1f;阿里云百科分享使用阿里云服务器常用的十大使用场景&#xff0c;说是十大场景实际上用途有很多&#xff0c;阿里云百科分享常见的云服务器使用场景&#xff0c;如本地搭建ChatGPT、个人网站或博客、运维测试、学习Linux、跑Python、小程…

6年测试,不断磨炼升级打怪自动化测试,一路晋升他终于冲出月35k+

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

ViT 论文逐段精读

https://www.bilibili.com/video/BV15P4y137jb Vision Transformer 挑战了 CNN 在 CV 中绝对的统治地位。Vision Transformer 得出的结论是如果在足够多的数据上做预训练&#xff0c;在不依赖 CNN 的基础上&#xff0c;直接用自然语言上的 Transformer 也能 CV 问题解决得很好…

如何学习web前端开发?这样学前端事半功倍,能救一个是一个!

非常理解想要自学前端的伙伴&#xff0c;因为好程序员的学员一开始也是自学插画的&#xff0c;很多同学&#xff0c;自学到最后真的非常枯燥乏味&#xff0c;且走了很多弯路。小源想着能帮一把是一把的原则&#xff0c;这两天整理了一份前端的高效学习路线&#xff0c;想学web前…

接口测试常用工具及测试方法(基础篇)

首先&#xff0c;什么是接口呢&#xff1f; 接口一般来说有两种&#xff0c;一种是程序内部的接口&#xff0c;一种是系统对外的接口。 系统对外的接口&#xff1a;比如你要从别的网站或服务器上获取资源或信息&#xff0c;别人肯定不会把数据库共享给你&#xff0c;他只能给…

Windows管理内存的3种方式——堆、虚拟内存、共享内存

一、操作系统管理内存概述 在 Windows 操作系统中&#xff0c;每个进程都被分配了 4GB 的虚拟地址空间&#xff0c;这被称为进程的虚拟地址空间。虚拟地址空间提供了一个抽象的地址空间&#xff0c;使得每个进程都可以认为它拥有自己的独立内存空间。这个虚拟地址空间被分为两…

720度沉浸式体验,VR虚拟展馆的价值有哪些?

展馆作为一个展示商品、会议交流、信息传播、经济贸易的场所&#xff0c;能够创造巨大的经济效益和社会效益。什么是VR虚拟展馆呢&#xff1f;VR虚拟展馆是基于VR全景技术打造的线上展厅&#xff0c;可以应用在多种领域中展示各式的商品和内容&#xff0c;观众通过VR虚拟展馆可…

ubuntu系统下使用ros控制UR真实机械臂,逻辑清晰,亲测有效

梳理一下在ubuntu系统使用ros控制UR真实机械臂的思路&#xff0c;逻辑清晰&#xff0c;亲测有效&#xff0c;并记录踩过的坑。从0开始&#xff0c;使用ros控制真实UR机械臂。 环境&#xff1a;ubuntu18.04 ros版本&#xff1a;melodic 机械臂型号&#xff1a;UR5e 一&#xff…

当我与单链表分手后,在酒吧邂逅了双向循环链表.....

链表的种类有8种&#xff0c;但我们最常用的为无头单向非循环链表和带头双向循环链表。 带头双向循环链表 当带头双向循环链表只有哨兵位头的时候&#xff0c;双向链表的指向如下图。 head->pre和head->next都是指向自己&#xff0c;这个是有巨大优势的&#xff0c;代码…

CTFHub | 文件包含

0x00 前言 CTFHub 专注网络安全、信息安全、白帽子技术的在线学习&#xff0c;实训平台。提供优质的赛事及学习服务&#xff0c;拥有完善的题目环境及配套 writeup &#xff0c;降低 CTF 学习入门门槛&#xff0c;快速帮助选手成长&#xff0c;跟随主流比赛潮流。 0x01 题目描述…

一篇文章让你上手Canal数据同步神技~

视频教程传送门&#xff1a; Canal极简入门&#xff1a;一小时让你快速上手Canal数据同步神技~_哔哩哔哩_bilibiliCanal极简入门&#xff1a;一小时让你快速上手Canal数据同步神技~共计13条视频&#xff0c;包括&#xff1a;01.课前导学与前置知识点、02.Canal组件了解、03.My…

光纤收发器可以连接光模块吗?

随着科技的进步发展&#xff0c;城市信息化速度的加快&#xff0c;光通信产品在数据中心和安防监控等场景中的运用越来越广泛&#xff0c;而这之间的连接则需要光模块和光纤收发器来实现。很多用户对光模块和光纤收发器的使用有些疑虑&#xff0c;两者该如何连接&#xff1f;又…

2023年5月实时获取地图边界数据方法,省市区县街道多级联动【附实时geoJson数据下载】

首先&#xff0c;来看下效果图 在线体验地址&#xff1a;https://geojson.hxkj.vip&#xff0c;并提供实时geoJson数据文件下载 可下载的数据包含省级geojson行政边界数据、市级geojson行政边界数据、区/县级geojson行政边界数据、省市区县街道行政编码四级联动数据&#xff0…

第7章链接:重定位、可执行目标文件、加载可执行目标文件

文章目录 7.7 重定位7.7.1 重定位表目7.7.2 重定位符号引用重定位PC相关的引用重定位绝对引用 7.8 可执行目标文件7.9 加载可执行目标文件 7.7 重定位 一旦链接器完成了符号解析这一步&#xff0c;它就把代码中的每个符号引用和确定的一个符号定义&#xff08;也就是&#xff…