前言
本文是我在学习代理模式时的一篇笔记,除了对代理模式、静态和动态代理的概念和实现进行了描述外,还有关于动态代理中的InvocationHandler的一些实验性的实例,测试了InvocationHandler中的proxy参数的来源、多个方法的接口得到的代理类的具体情况以及代理类的生成。
代理模式
概述
代理模式是一种常用的设计模式,它为其他对象提供一个代理以控制对某个对象的访问。代理对象负责为实际的处理对象进行一些数据预处理等工作。根据代理类的生成时间不同,代理模式也可以分为静态代理和动态代理
详解
代理模式一般涉及到的角色有四种:
1.主题接口:定义代理类和真实主题的方法的公共对外方法,也是代理类代理真实主题的方法。
2.真实主题:真正实现业务逻辑的类。
3.代理类:用来代理和封装真实主题的类。
4.客户端:使用代理类提供的接口进行工作。
代理的优点
- 隐藏委托类的实现,调用者只需要与代理类进行交互。
- 解耦,对业务逻辑的改变只需要改变代理类和委托类,不需要对客户端类进行修改
代理模式的使用场景
代理模式的典型使用场景如:struts2中的action调用,hibernate的懒加载,spring的aop等。大体分为以下几种:
1.在原方法执行之前和之后做一些相关操作,可以使用代理实现。例如记录日志和数据预处理。
2.封装真实的主题类,隐藏真实的业务逻辑接口,只暴露给使用者公用的接口。
3.还可以应用于延迟加载。
静态代理
概念
静态代理是在代码编译后程序运行前就存在代码的字节码文件,代理类和委托类的关系已经确定好了。
一个例子
一个房主想要出售自己的房子,但是房主并没有时间亲自去出售,所以他找了一个代理帮他去寻找买主。
首先定义一个Sales接口来表示这个对象可以销售房子:
public interface Sales{
public void sale();
}
房主类
Class Owner implements Sales{
@Override
public void sale(){
System.out.println("房主销售房子");
}
}
被委托的中介类:
Class Agent impelments Sales{
private Owner owner;
@Override
public void sale(){
System.out.println("中介联系买家,协商卖房事宜并联系房主");
owner.sale();
System.out.println("卖房完成,中介收取中介费");
}
}
可以看出,当调用中介类Agent
中的````sale```方法时,中介经过自己的处理后调用自己所对应的房主类的同名方法进行真正的业务逻辑,在卖房完成后再进行后续处理(收取中介费)。
在这种情况下的买家类写法如下:
Class Customer {
public void buy{
Sales sale = new Agent();
sale.sale();
}
}
客户类只需要调用中介类的售卖方法就可以完成购买的过程,对于客户来说,只需要知道中介类的相应方法即可,不需要关心到底是谁执行了业务逻辑。
静态代理的局限
由于客户端是调用的主题接口进行操作的,所以同一个代理类只能代理一个功能。如果存在多个需要代理的接口,并且代理的步骤相同只是方法调用不同,那么静态代理会导致代码十分臃肿。
如果接口增加一个新的方法,那么委托类和代理类都需要实现这个方法,维护相对比较复杂。
动态代理
使用动态代理需要一个InvocationHandler接口和一个Proxy类。
InvocationHandler接口是创建委托类和代理类关系的连接的中间类必须实现的接口,其唯一的方法Invoke方法用于集中处理代理类对象上的方法调用,在该方法上通常实现对委托类的代理访问。
Proxy类是Java动态代理机制的核心工具类,它提供了一组静态方法来动态生成代理类以及代理类对象。
相关类详解
InvocationHandler
InvocationHandler接口只需要实现一个方法,即public Object invoke(Object proxy,Method method,Object[] params);
这个方法用来组装代理类。
Proxy
Proxy类提供了一系列静态方法,用来提供代理类的组装功能
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
实现步骤
1.实现InvocationHandler接口定义自己的调用处理器。
2.通过Proxy类的newProxyInstance方法指定代理类的ClassLoader对象和代理要实现的Interface以及调用处理器InvocationHandler对象,从而创建动态代理的对象。
一个实例
使用动态代理的方式实现上边的售房过程:
售房接口
public interface Sales{
public void sale();
}
售房委托类
public class Owner implements Sales{
@Override
public void sale(){
System.out.println("房主卖房");
}
}
invocation handler类
public class ReadyInvocationHandler implements InvocationHandler{
private Object ins;
public ReadyInvocationHandler(Object ins){
this.ins = ins;
}
@Override
public Object invoke(Object proxy,Method method,Object[] params){
Object result = null;
System.out.println("中介与客户讨论买方事宜,联系房主");
method.invoke(ins,params);
System.out.println("房子卖出,中介收取中介费");
return result;
}
}
客户类
public class Customer{
public static void main(String[] args){
Sales sale = Proxy.newProxyInstance(Owner.class.getClassLoader,Owner.class.getInterfaces(),new ReadyInvocationHandler(new Owner));
sale.sale();
}
}
客户类的运行结果如下
中介与客户讨论买方事宜,联系房主
房主卖房
房子卖出,中介收取中介费
结论
InvocationHandler是定义在代理类组装过程中生成代理类的相应接口方法的接口,在接口的invoke方法中,method.invoke(object,params)
的调用即代表静态代理中代理类对委托类的相应接口方法的调用,在invoke的调用之前的操作即是代理类中对数据的预操作,之后的操作是代理类中后续的操作。而Proxy类在生成代理类时,就通过invoke来组装相应的代理类,生成字节码并加载实例化。
当一个接口有多个方法时的情况
主题接口
public interface Multi {
public void methodA();
public void methodB();
}
委托类
public class InstanceMulti implements Multi {
@Override
public void methodA() {
System.out.println("a");
}
@Override
public void methodB() {
System.out.println("b");
}
}
InvocationHandler实现
public class MultiInvocationHandler implements InvocationHandler {
private Object ins;
public MultiInvocationHandler(Object ins) {
this.ins = ins;
}
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
System.out.println("before");
Object result = arg1.invoke(ins, arg2);
System.out.println("after");
return result;
}
}
客户类
public class CustomerMulti {
public static void main(String[] args) {
Multi m1 = (Multi) Proxy.newProxyInstance(InstanceMulti.class.getClassLoader(), InstanceMulti.class.getInterfaces(), new MultiInvocationHandler(new InstanceMulti()));
Saleable s = (Saleable) Proxy.newProxyInstance(Owner.class.getClassLoader(), Owner.class.getInterfaces(), new ReadyInvocationHandler(new Owner()));
System.out.println(m1.getClass().getName());
System.out.println(s.getClass().getName());
m1.methodA();
m1.methodB();
s.sale();
}
}
这里加入了之前写的单方法接口的一个代理来进行对比。
运行结果
com.sun.proxy.$Proxy0
com.sun.proxy.$Proxy1
before
a
after
before
b
after
com.sun.proxy.$Proxy1
和客户谈合同,准备联系房主
房主卖房
完事之后收取中介费
结果表明了,对于多个方法的接口,一个InvocationHandler会规定所有的代理类方法的组装方式。
另外关于InvocationHanlder中的proxy,在结果中地就行是我在InvocationHandler中输出的proxy的类名称,在第二行输出的是得到的代理类名称。可以明显看出,InvocationHandler中的proxy是最后实际的代理类,而最终得到的代理类是JVM在运行过程中临时生成的,名称格式是$Proxy+编号。