0%
这是一片思考的空间 -- arthinking
Spring 重构&代码整洁之道 软件设计 JVM 并发编程 数据结构与算法 分布式 存储 网络 微服务 设计模式
Java技术栈 - 涉及Java技术体系

Java基础笔记 - 动态代理 Java中动态代理类的介绍和使用

我们知道,通过使用静态代理时,真实角色必须是存在的,并将其作为代理对象的内部属性,不能在程序中动态的调用真实角色。在事先并不知道真实角色的情况先,如果要使用代理,可以使用Java的动态代理类来解决。

与静态代理类的创建不同的是生成代理的方式,这里使用了java.lang.reflect.Proxy类的newProxyInstance()方法生成一个代理,并且在生成过程中需要传入一个代理处理器,java.lang.reflect.InvocationHandler的一个实现类。

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException

其中生成的代理类是由Java内部执行的。

1、Java动态代理涉及到的类:

public interface InvocationHandler

InvocationHandler 是代理实例的调用处理程序 实现的接口。

每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。

invoke
Object invoke(Object proxy,
Method method,
Object[] args)
throws Throwable

在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。

参数:

  • proxy - 在其上调用方法的代理实例
  • method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
  • args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。

返回:
从代理实例的方法调用返回的值。如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出 NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出 ClassCastException。

抛出:
Throwable - 从代理实例上的方法调用抛出的异常。该异常的类型必须可以分配到在接口方法的 throws 子句中声明的任一异常类型或未经检查的异常类型 java.lang.RuntimeException 或 java.lang.Error。如果此方法抛出经过检查的异常,该异常不可分配到在接口方法的 throws 子句中声明的任一异常类型,代理实例的方法调用将抛出包含此方法曾抛出的异常的 UndeclaredThrowableException。

public class Proxy extends Object implements Serializable

Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

Proxy

protected Proxy(InvocationHandler h)

使用其调用处理程序的指定值从子类(通常为动态代理类)构建新的 Proxy 实例。

参数:
h - 此代理实例的调用处理程序

getProxyClass

public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException

返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。该代理类将由指定的类加载器定义,并将实现提供的所有接口。如果类加载器已经定义了具有相同排列接口的代理类,那么现有的代理类将被返回;否则,类加载器将动态生成并定义这些接口的代理类。

对可以传递给 Proxy.getProxyClass 的参数有以下几个限制:

  • interfaces 数组中的所有 Class 对象必须表示接口,而不能表示类或基本类型。
  • interfaces 数组中的两个元素不能引用同一 Class 对象。
  • 所有接口类型的名称通过特定的类加载器必须可见。换句话说,对于类加载器 cl 和所有接口 i,以下表达式必须为 true:
    Class.forName(i.getName(), false, cl) == i
  • 所有非公共接口必须位于同一包中;否则,该代理类将不可能实现所有的接口,无论它在哪一个包中定义。
  • 对于有相同签名的指定接口中任何成员方法集:
    • 如果任何方法的返回类型是基本类型或 void,那么所有的方法必须具有与此相同的返回类型。
      否则,该方法之一必须是返回类型,它可以指派给该方法其余的所有返回类型。
    • 得到的代理类必须不超过虚拟机在类上施加的任何限制。例如,虚拟机可以限制某一类实现至多 65535 的接口数;在这种情况下,interfaces 数组的大小必须不超过 65535。
    • 如果违反了这些限制,Proxy.getProxyClass 将抛出 IllegalArgumentException。如果 interfaces 数组参数或其任何元素为 null,则将抛出 NullPointerException。

注意,指定的代理接口的顺序非常重要:对接口组合相同但顺序不同的代理类的两个请求会导致两个不同的代理类。

参数:

  • loader - 定义代理类的类加载器
  • interfaces - 代理类要实现的接口列表

返回:
用指定的类加载器定义的代理类,它可以实现指定的接口

抛出:

  • IllegalArgumentException - 如果违反传递到 getProxyClass 的参数上的任何限制
  • NullPointerException - 如果 interfaces 数组参数或其任何元素为 null

newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException

返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。此方法相当于:

1
2
3
Proxy.getProxyClass(loader, interfaces).
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });

Proxy.newProxyInstance 抛出 IllegalArgumentException,原因与 Proxy.getProxyClass 相同。

参数:
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序
返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
抛出:
IllegalArgumentException - 如果违反传递到 getProxyClass 的参数上的任何限制
NullPointerException - 如果 interfaces 数组参数或其任何元素为 null,或如果调用处理程序 h 为 null

isProxyClass

public static boolean isProxyClass(Class<?> cl)当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。
此方法的可靠性对于使用它做出安全决策而言非常重要,所以此方法的实现不应仅测试相关的类是否可以扩展 Proxy。

参数:
cl - 要测试的类
返回:
如该类为代理类,则为 true,否则为 false
抛出:
NullPointerException - 如果 cl 为 null

getInvocationHandler

public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException返回指定代理实例的调用处理程序。

参数:
proxy - 返回调用处理程序的代理实例
返回:
代理实例的调用处理程序
抛出:
IllegalArgumentException - 如果参数不是一个代理实例

**动态代理类:**在运行时生成的class,在其生成过程中,你必须提供一组接口给它,然后该class就声称实现了这些接口。可以把该class的实例当做这些接口中的任何一个来用。其实,这个Dynamic Proxy就是一个Proxy,他不会替你做任何实质性的工作。在生成它的实例时,必须提供一个Handler,由它接管实际的工作。

在使用动态代理类时,必须实现InvocationHandler接口。

通过DynamicProxy,RealSubject可以在运行时动态改变,需要控制的接口Subject也可以在运行时改变,控制的方式DynamicSubject类也可以动态改变,从而实现了非常灵活的动态代理关系。

2、动态代理使用场合:

调试,远程方法调用

3、实现一个动态代理类的过程:

① 提供一个抽象角色即被代理类的接口:

1
2
3
public interface Subject {
abstract public void request();
}

② 通过抽象角色实现一个真实角色,即被代理类

1
2
3
4
5
6
7
8
public class RealSubject implements Subject {

public RealSubject() {}

public void request() {
System.out.println("真实的请求.");
}
}

③ 创建一个实现了接口InvocationHandler的类,并实现其invoke方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DynamicSubject implements InvocationHandler {
private Object sub;

public DynamicSubject() {}

public DynamicSubject(Object obj) {
sub = obj;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

method.invoke(sub, args);
return null;
}
}

④ 在客户端通过Proxy的静态方法newProxyInstance()创建一个代理,并通过代理调用方法:

1
2
3
4
5
6
7
8
9
10
// 创建被代理类
RealSubject rs = new RealSubject();
// 通过被代理类初始化一个代理处理器,在newProxyInstance()中作为参数生成代理
InvocationHandler ds = new DynamicSubject(rs);
Class<?> cls = rs.getClass();
//生成代理对象
Subject subject = (Subject) Proxy.newProxyInstance(
cls.getClassLoader(), cls.getInterfaces(), ds);
//通过代理对象调用真实方法
subject.request();

欢迎关注我的其它发布渠道

订阅IT宅
内功修炼
Java技术栈
Java架构杂谈是IT宅精品文章公众号,欢迎订阅:
📄 网络基础知识:两万字长文50+张趣图带你领悟网络编程的内功心法 📄 HTTP发展史:三万长文50+趣图带你领悟web编程的内功心法 📄 HTTP/1.1:可扩展,可靠性,请求应答,无状态,明文传输 📄 HTTP/1.1报文详解:Method,URI,URL,消息头,消息体,状态行 📄 HTTP常用请求头大揭秘 📄 HTTPS:网络安全攻坚战 📄 HTTP/2:网络安全传输的快车道 📄 HTTP/3:让传输效率再一次起飞 📄 高性能网络编程:图解Socket核心内幕以及五大IO模型 📄 高性能网络编程:三分钟短文快速了解信号驱动式IO 📄 高性能网络编程:彻底弄懂IO复用 - IO处理杀手锏,带您深入了解select,poll,epoll 📄 高性能网络编程:异步IO:新时代的IO处理利器 📄 高性能网络编程:网络编程范式 - 高性能服务器就这么回事 📄 高性能网络编程:性能追击 - 万字长文30+图揭秘8大主流服务器程序线程模型
📄 Java内存模型:如果有人给你撕逼Java内存模型,就把这些问题甩给他 📄 一文带你彻底理解同步和锁的本质(干货) 📄 AQS与并发包中锁的通用实现 📄 ReentrantLock介绍与使用 📄 ReentrantReadWriteLock介绍与使用 📄 ReentrantLock的Condition原理解析 📄 如何优雅的中断线程 📄 如何优雅的挂起线程 📄 图解几个好玩的并发辅助工具类 📄 图解BlockingQueue阻塞队列
📄 消息队列那么多,为什么建议深入了解下RabbitMQ? 📄 高并发异步解耦利器:RocketMQ究竟强在哪里? 📄 Kafka必知必会18问:30+图带您看透Kafka
📄 洞悉MySQL底层架构:游走在缓冲与磁盘之间 📄 SQL运行内幕:从执行原理看调优的本质 📄 洞悉Redis技术内幕:缓存,数据结构,并发,集群与算法