【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

相关文章
|
5天前
|
Java 测试技术 数据库
【JAVA基础篇教学】第十七篇:Java单元测试
【JAVA基础篇教学】第十七篇:Java单元测试
|
5天前
|
Java 索引
【JAVA基础篇教学】第七篇:Java异常类型说明
【JAVA基础篇教学】第七篇:Java异常类型说明
|
5天前
|
存储 Java
【JAVA基础篇教学】第一篇:Java基础数据类型
【JAVA基础篇教学】第一篇:Java基础数据类型
|
3天前
|
Java
Java中int[]与Integer[]相互转化的方法,java基础知识面试重点总结
Java中int[]与Integer[]相互转化的方法,java基础知识面试重点总结
SpringJDK动态代理实现,2024Java面试真题精选干货整理
SpringJDK动态代理实现,2024Java面试真题精选干货整理
|
5天前
|
SQL Java 关系型数据库
【JAVA基础篇教学】第十六篇:Java连接和操作MySQL数据库
【JAVA基础篇教学】第十六篇:Java连接和操作MySQL数据库
|
5天前
|
XML Java 数据库连接
【JAVA基础篇教学】第十五篇:Java中Spring详解说明
【JAVA基础篇教学】第十五篇:Java中Spring详解说明
|
5天前
|
设计模式 Java
【JAVA基础篇教学】第十四篇:Java中设计模式
【JAVA基础篇教学】第十四篇:Java中设计模式
|
5天前
|
Java
【JAVA基础篇教学】第十三篇:Java中I/O和文件操作
【JAVA基础篇教学】第十三篇:Java中I/O和文件操作
|
5天前
|
Java
【JAVA基础篇教学】第十二篇:Java中多线程编程
【JAVA基础篇教学】第十二篇:Java中多线程编程