【Java基础】细说java动态代理及使用场景

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

一、定义

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;
    }
}
  • 第二步:新建一个交易接口及其实现类
    ```java
    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 完成代理对象的创建

```java

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

相关文章
|
2月前
|
Java Linux
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
65 2
|
4天前
|
安全 Java 数据安全/隐私保护
有哪些场景不适合使用Java反射机制
Java反射机制虽强大,但并非万能。在性能要求极高、安全性严格控制、类结构复杂多变或对象创建频繁的场景下,使用反射可能带来性能下降、安全风险增加等问题,应谨慎选择。
|
15天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
25天前
|
缓存 监控 Java
Java 线程池在高并发场景下有哪些优势和潜在问题?
Java 线程池在高并发场景下有哪些优势和潜在问题?
|
26天前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
23 0
[Java]静态代理与动态代理(基于JDK1.8)
|
28天前
|
Java 数据处理
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
420 37
|
1月前
|
前端开发 小程序 Java
java基础:map遍历使用;java使用 Patten 和Matches 进行正则匹配;后端传到前端展示图片三种情况,并保存到手机
这篇文章介绍了Java中Map的遍历方法、使用Pattern和matches进行正则表达式匹配,以及后端向前端传输图片并保存到手机的三种情况。
20 1
|
1月前
|
Java
深入理解Java动态代理
深入理解Java动态代理
19 1
|
1月前
|
Java 数据处理 数据库
Java多线程的理解和应用场景
Java多线程的理解和应用场景
52 1
下一篇
无影云桌面