Java 动态代理的原理和特性

it2024-06-29  41

文章目录

1 代理模式的作用2 静态代理3 动态代理3.1 Proxy类3.2 InvocationHandler接口3.3 例子3.4 代理类的特性

1 代理模式的作用

代理模式的作用主要在于在于生成代理对象,在服务提供方和使用方之间充当一个媒介,控制真实对象的访问。

假定目前项目实现了成百上千个类,实现了不同类型的功能,现在需要在实现每个功能的基础上,打印出功能的详细信息,比如方法名、功能、参数等,不使用代理技术,就需要一个一个修改成百上千个类,而且当又不需要打印额外信息时,又需要修改源代码。

2 静态代理

静态代理的做法是为每个类编写一个对应的代理类,让他们实现同一个接口,接口中定义了要实现的函数。 静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。 但是缺点也十分明显,一旦目标接口增加方法,需要重新维护代理类和目标类,工作量巨大。

3 动态代理

如何理解动态代理?

JDK反射库java.lang.reflect中提供了InvocationHandler接口和Proxy类,以实现动态代理

3.1 Proxy类

首先讨论Proxy类,Proxy类提供静态方法创建动态代理类和实例,是所有动态代理类的超类。想要创建一个代理对象,需要使用Proxy类的静态工厂方法newProxyInstance:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)

该方法的三个参数:

类加载器。作为Java安全模型的一部分,对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。null表示使用默认的类加载器。类加载器参数定义了由哪个类加载器对象对生成的代理对象进行加载一个Class对象数组,每个元素都是需要实现的接口。表示给需要代理的对象提供一组接口,使代理对象实现这些接口,即多态一个调用处理器,实现了InvocationHandler接口的类对象。当动态代理对象调用某个方法时,会被转发到处理器中invoke方法中进行调用

3.2 InvocationHandler接口

现在讨论InvocationHandler接口,实现该接口的类对象作为处理器参数传入代理类对象中,其中的invoke方法负责接收代理对象转发来的方法,在invoke方法中可以对接收的方法进行额外的处理,比如打印信息、记录日志等,然后返回该方法的处理结果即可(通过Method.invoke方法得到)。

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

invoke方法中有三个参数:

代理类传入的方法参数列表

动态代理的一点重要作用就是,在方法传入之前,你不知道传入方法的信息,但是却可以对这些信息定义额外的处理。

3.3 例子

以Java核心思想卷1 第10版书中的例子进行说明(程序清单6-10)

通过二分法从一个整数数组中查找目标值,直到找到位置,这可以通过Arrays.binarySearch实现。现在如果额外需要打印出来具体的查找过程,通过动态代理来实现:

首先定义针对“打印二分查找方法信息”的处理器类:

/** * An invocation handler(调用处理器) that prints out the method name and parameters , * then invoke the original method */ class TraceHandler implements InvocationHandler { //被代理的真实对象 private Object target; /** * Constructs a TraceHandler * @param t the implicit parameter of the method call */ public TraceHandler(Object t) { target = t; } /** * print the method's information then invoke the original method * @param proxy * @param method the original method * @param args original method's arguments * @return return the result the original method's invocation * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.print(target); System.out.print("." + method.getName() + "("); if (args != null) { for (int i = 0; i < args.length; i++) { System.out.print(args[i]); if (i < args.length - 1) System.out.print("."); } } System.out.println(")"); return method.invoke(target,args); } }

可以看出,在调用Method.invoke方法之前,对当前被代理对象值(Integer)、调用方法(compareTo)、以及传入的参数(Integer)进行打印。

接下来,为1000个整数数组中每个整型对象创建代理对象,然后将代理整型对象数组作为参数传入Arrays.binarySearch方法中:

public class ProxyTest { public static void main(String[] args) { Object[] elements = new Object[1000]; //fill elements with proxies for the integers 1 ... 1000 for (int i = 0; i < 1000; i++) { Integer value = i + 1; InvocationHandler handler = new TraceHandler(value); Object proxy = Proxy.newProxyInstance(null,new Class[] { Comparable.class }, handler); //也可以改为 /* Object proxy = Proxy.newProxyInstance(handler.getClass().getClassLoader(),Integer.class.getInterfaces(), handler); */ elements[i] = proxy; } //construct a random integer Integer key = new Random().nextInt(elements.length) + 1; //search for the key int result = Arrays.binarySearch(elements,key); //print match if found if (result >= 0) System.out.println(elements[result]); } }

运行结果: 可以看出,最后一步的打印elements[result]值涉及到Integer.toString方法,该方法也被整型代理类的处理器接收到了,并打印出来了。

3.4 代理类的特性

代理类是在程序运行过程中创建的,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有什么区别所有的代理类都扩展自超类Proxy,一个代理类只有一个实例域,即调用处理器。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。没有定义代理类的名字,虚拟机中的Proxy类将生成一个$Proxy开头的类名,这一点可以通过反射机制来测试对于特定的类加载器和预设的一组接口来说,只能有一个代理类。即如果使用同一个类加载器和接口数组调用两次newProxyInstance方法,只能得到同一个类的两个对象(比如3.3中生成了1000个整型代理对象,它们属于同一个代理类)代理类一定是final和public,可以通过Proxy类中的isProxyClass检测一个特定的Class对象是否属于一个代理类
最新回复(0)