代理模式
在讲动态代理之前,需要了解什么是代理模式,经典的代理模式的uml图相信大家都见过:
代理模式
RealSubject是需要被代理的对象。我们要在RealSubject之外增加一些处理,就增加一个Proxy类,实现Subject接口,在Proxy类里持有RealSubject的实例。
Client的请求全部打到Proxy实例上,由Proxy实例来控制是否将请求转发给RealSubject,或者做额外的处理。
代理模式的优点:对于外界来讲,完全无感知,耦合性低。
看一下实例代码:
public class IntegerProxy implements Comparable<Integer> {
private Integer i;
public IntegerProxy(Integer i) {
this.i = i;
}
@Override
public int compareTo(Integer o) {
System.out.println(i + "compareTo(" + o + ")");
return i.compareTo(o);
}
}
这里对Integer类做了一层代理。这个类的作用在于,在compareTo请求之上增加了信息的打印输出。
在实际场景中,如果我们需要对多个方法都要做类似的处理,在每个地方都增加同样的代码,就显得有点不够优雅了。这时候,可以使用java的动态代理。
常用的动态代理有两种实现方式:JDK和CGLIB
JDK动态代理
看名字就可以知道,这种动态代理是JDK本身就支持的,需要借助java.lang.reflect下的接口来实现。
创建代理对象
要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
这个方法有三个参数:
- 类加载器。作为Java安全模型的一部分,对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。
- Class对象数组,每个元素都是需要实现的接口。
- 调用处理器。
调用处理器
调用处理器是实现了InvocationHandler接口的类对象。在这个接口中只有一个方法:
/**
* @param proxy the proxy instance that the method was invoked on
*/
Object invoke(Object proxy, Method method, Object[] args)
动态代理类,需要实现此接口,在接口方法的实现里做代理逻辑的处理。
创建动态代理
创建一个动态代理对象的工作如下:
- 获取 RealSubject上的所有接口列表;
- 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ;
- 根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码;
4 将对应的字节码转换为对应的class 对象; - 创建InvocationHandler 实例handler,用来处理Proxy所有方法调用;
- Proxy的class对象以创建的handler对象为参数,实例化一个proxy对象。
接下来我们看一段代码实现:
class TraceHandler implements InvocationHandler {
private Object target;
public TraceHandler(Object t) {
target = t;
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
{
// print implicit argument
System.out.print(target);
// print method name
System.out.print("." + m.getName() + "(");
// print explicit arguments
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(")");
// invoke actual method
return m.invoke(target, args);
}
}
public class Main {
public static void main(String[] args) {
Integer value = 10;
// 1.获取对应的ClassLoader
ClassLoader classLoader = value.getClass().getClassLoader();
// 2.获取Integer 所实现的所有接口
Class[] interfaces = value.getClass().getInterfaces();
// 3.设置一个来自代理传过来的方法调用请求处理器,处理所有的代理对象上的方法调用
InvocationHandler handler = new TraceHandler(value);
/*
4.根据上面提供的信息,创建代理对象 在这个过程中,
a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
b.然后根据相应的字节码转换成对应的class,
c.然后调用newInstance()创建实例
*/
Comparable proxy = (Comparable) Proxy.newProxyInstance(classLoader, interfaces, handler);
proxy.compareTo(11);
}
}
/* output:
compareTo
10.compareTo(11)
*/
那如何TraceHandler有什么用呢?
利用TraceHandler可以打印出二分查找的查找顺序:
Object[] elements = new Object[1000];
// fill elements with proxies for the integers 1 . . . 1000
for (int i = 0; i < elements.length; i++) {
Integer value = i + 1;
elements[i] = Proxy.newProxyInstance(..); // proxy for value;
}
// 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]);
/* output:
500.compareTo(288)
250.compareTo(288)
375.compareTo(288)
312.compareTo(288)
281.compareTo(288)
296.compareTo(288)
288.compareTo(288)
288.toString()
*/
为何toString也会被代理
细心的读者会发现,上面的程序执行最后有一行
288.toString()
为什么toString方法也会被代理呢,我们代理的接口里,并没有声明toString的方法呀。
这里有两点我们需要知道:
- 所有的代理类都扩展于Proxy类;
- 所有的代理类都覆盖了Object类中的方法toString、equals和hashCode;
针对上面的第二点,所有对代理类的toString、equals和hashCode的方法调用,都会请求到invoke代理实现上去。所以就出现toString方法也被代理的情况了。
参考资料:
《Java核心技术 卷I》
Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
lombok的使用和原理
Lombok原理分析与功能实现
网友评论