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

发布于 2011-10-09 | 更新于 2020-09-20

我们知道,通过使用静态代理时,真实角色必须是存在的,并将其作为代理对象的内部属性,不能在程序中动态的调用真实角色。在事先并不知道真实角色的情况先,如果要使用代理,可以使用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();

本文作者: arthinking

本文链接: https://www.itzhai.comjava-based-notebook-the-dynamic-proxy-in-java-dynamic-proxy-class-introduction-and-use-of.html

版权声明: 版权归作者所有,未经许可不得转载,侵权必究!联系作者请加公众号。

×
IT宅

关注公众号及时获取网站内容更新。