代理模式
假如一个A类具备做甲事情的能力,我们希望它做甲事情之前或之后做多一些操作(比如记录日志),同时又不想去修改A类的结构(因为A类可能有很多其它引用,如果一改A类,所有地方都变动了)。这时候怎么办?
针对这个情况,我们可以在A类外面包多一个类(叫它B类吧),B类同样实现甲方法,B类的甲方法里调用了A类的甲方法,除此之外,B类的甲方法中还额外加多一些操作。随后,调用B类的甲方法,就等同于调用A类的甲方法,同时也执行了我们想要扩展的操作。
一个比较形象的比喻,如果A类是明星,B类就是经纪人,我们想叫明星来演出,跟经纪人说就能实现明星演出这件事,但经纪人在安排明星演出这件事中,可以做其它一些额外的事情,比如出场费商讨、安保工作或行程安排等。经纪人就是明星的代理人,这里的B类就是A类的代理类。
这就是代理模式的概念。来个代码Demo。
定义一个明星接口StarObject
public interface StarInterface {
public void show(String name);
}
Star实现StarInterface接口,实现一些业务逻辑操作
public class Star implements StarInterface {
@Override
public void show(String name) {
System.out.println("我是明星" + name + ",我在演出。");
}
}
实现经纪人类,即代理类
public class Broker implements StarInterface {
private StarInterface star;
public Broker(StarInterface star) {
this.star = star;
}
@Override
public void show(String name) {
System.out.println("我是经纪人,现在去叫" + name + "来演出。");
star.show(name);
System.out.println("我是经纪人," + name + "演出结束,谢谢大家。");
}
}
运行代码
StarInterface star = new Broker(new Star());
star.show("Leslie Zhang");
得到结果:
我是经纪人,现在去叫Leslie Zhang来演出。
我是明星Leslie Zhang,我在演出。
我是经纪人,Leslie Zhang演出结束,谢谢大家。
这就是代理模式了。但注意,上面所述的是指静态代理模式。有静态就会有动态,那动态代理是什么?
动态代理
上面的方式对于一个类扩展出一个代理类的情况来说,是可以解决问题了。但如果之后对“明星”类要实现更多的“经纪人”类时,我们都需要再写多个代理类,这会变得麻烦和复杂。JDK为此给出了动态代理的方案。
先看Demo。
实现一个动态代理类
public class BrokerDynamic implements InvocationHandler {
private Object star;
public BrokerDynamic(Object star) {
this.star = star; // 传入实现类。
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("这里是经纪人在明星演出前的前置处理。");
Object result = method.invoke(star, args); // 得到并返回方法值
System.out.println("这里是经纪人在明星演出后的后置处理。");
return result;
}
// 值得注意的是,这个动态代理类中,对象成员、方法调用返回值类型、方法参数都是Object类型
// 这表示这个代理类能适用多种类型。
// 在使用时通过强制转换类型就可以得到指定的对象了。
}
运行代码
StarInterface star = (StarInterface) Proxy.newProxyInstance(StarInterface.class.getClassLoader()
,new Class[] {StarInterface.class}
, new BrokerDynamic(new Star()));
star.show("Leslie Zhang");
// 在调用star的show()方法时,代理类会转为去调用BrokerDynamic类invoke方法,
// invoke方法的参数proxy就是发起调用的代理类star实例,
// method就是指show()方法,
// args就是调用show()方法时传的参数"Leslie Zhang"
得到运行结果
这里是经纪人在明星演出前的前置处理。
我是明星Leslie Zhang,我在演出。
这里是经纪人在明星演出后的后置处理。
Proxy.newProxyInstanc是JDK提供的得到一个动态代理类的静态方法。
第一个参数为类加载器,我们这里使用StarInterface接口,就直接得到它对应的类加载器;
第二个参数是指实类所实现的接口列表,我们这里只实现StarInterface接口,故只传一个;
第三个参数为一个实现了调用处理器InvocationHandler接口的实例。
Proxy.newProxyInstanc返回对应类型的代理类,因为返回值为Object类型,所以需要强转一下。
得到代理类后,调用代理类对应的方法,这一个代理类就会自动地调用InvocationHandler实例的invoke方法。这样,我们就可以在invoke里面实现一些扩展操作了。
这就是动态代理的基本概念。
也许你还有这样的问题
- 上面的例子倒是很动态,其它的类一传进来都可以生成它对应的代理类,但前置处理和后置处理的操作写死了,并不灵活。
- InvocatioHandler中的invoke方法第一个参数proxy就是代理类实例了,在BrokerDynamic类中invoke方法里,直接调用proxy的方法不就可以了,为什么在构造方法中还要另外存一个star实例。
第一个问题确实存在一点局限,或许我们可以用两个抽象方法在BrokerDynamic类的invoke方法中分别作为前置和后置处理,之后再创建子类实类把这两个“坑”补上,便可实现前置后置处理的不同需求。
第二个问题中,形参proxy是指代理类(经纪人),即调用show方法时的代理类实例本身,不是原来的实类(明星),如果我们在invoke方法中,把Object result = method.invoke(star, args); 中的star换为proxy中进行调用,就会再执行一次invoke方法,invoke方法里又是proxy在调用show方法,会形成递归循环。所以,在BrokerDynamic里,我们还是需要另存一个原始实类(明星),在invoke方法中,调用原始实类对应的方法。