Proxy模式初探
Proxy译为:代理。生活中有许多 代理 的例子,现在假设这样一个场景:小A怕自己有些忙不过来,于是小B成为小A的代理人,当有一些简单的事情到来(小A和小B都可以处理)时,小B可以 代替 小A完成,但当有一些事情是小B无法完成必须 被代理人 小A出面解决时,小B才会通知小A让小A解决。
过程中,小A就是一个 被代理的对象 ,而小B则是一个代理小A的 代理对象 。下面举一个具体的示例入门代理模式。
现在有一个播放器 RealPlayer 可以播放音乐,它实现了接口 Playable ,有另外一个代理播放器 ProxyPlayer ,它可以返回音乐名称、修改音乐名称,但无法播放音乐,这样代理对象无法处理的任务则需要交给被代理对象 RealPlayer 完成。
Proxy模式类图
Playable接口
Playable接口定义了获取音乐名称、设置音乐名称、播放音乐3个方法。在被代理对象中,播放音乐功能是它特有的,代理对象必须经过被代理对象即 RealPlayer 才能实现播放功能。这里所指的代理对象不具有播放音乐的功能,并不是说它真的不具有,而是为了演示在实际中,一些 被代理对象 可以做到的事情且在不修改被代理对象的情况下 代理对象 做不到的事情,需要代理对象去调用被代理对象相应的方法才能实现。
public interface Playable {
String getMusicName();
void setMusicName(String musicName);
void playMusic();
}
被代理对象RealPlayer
被代理对象实现Playable接口,同时重写方法,播放的方法playMusic是被代理对象独有的。
public class RealPlayer implements Playable {
private String musicName;
@Override
public String getMusicName() {
return musicName;
}
@Override
public void setMusicName(String musicName) {
this.musicName = musicName;
}
@Override
public void playMusic() {
System.out.println("播放-" + musicName);
}
}
代理对象ProxyPlayer
代理对象是代理模式的主角。代理对象是代理被代理对象完成某些任务的,在本例中,即代替被代理对象完成返回音乐名称、设置音乐名称。但真正需要播放音乐时,就需要被代理对象来完成,因此代理对象需要持有被代理对象。但被代理对象并不是在代理对象创建的时候创建的,当用户只是需要获取音乐名称或设置音乐名称而不播放音乐时,被代理对象实际上无需创建,只有当需要被代理对象RealPlayer来播放音乐时,才会调用realize方法实例化被代理对象。
public class ProxyPlayer implements Playable{
private String musicName;
private RealPlayer realPlayer;//持有被代理对象
@Override
public String getMusicName() {
return musicName;
}
@Override
public synchronized void setMusicName(String musicName) {
//若被代理对象已实例化则同时设置musicName
if (realPlayer != null) {
realPlayer.setMusicName(musicName);
}
this.musicName = musicName;
}
/**
* 这是代理对象无法自己完成的任务
* 交给被代理对象 RealPlayer去完成
*/
@Override
public void playMusic() {
realize();
realPlayer.playMusic();
}
/**
* 实例化被代理对象
*/
private synchronized void realize() {
if(realPlayer == null) {
realPlayer = new RealPlayer();
realPlayer.setMusicName(musicName);
}
}
}
Main类
主方法类中,实例化一个编译类型为Playable,运行类型为ProxyPlayer的对象player。当调用获取音乐名称、设置音乐名称时,不会创建被代理对象,当调用playMusic方法时,才真正实例化被代理对象,并通过实例化的被代理对象调用其播放音乐的方法。
public class Main {
public static void main(String[] args) {
Playable player = new ProxyPlayer();
player.setMusicName("小酒窝");
player.getMusicName();
player.playMusic();
}
}
Proxy模式中的角色
- Subject 主体
Subject 保证实际主体和代理人共同拥有的方法,在例子中即为Playable接口
- RealSubject 实际的主体
即被代理对象,在本例中为 RealPlayer
- Proxy 代理人
代理对象,在本例中为 ProxyPlayer
- Client 请求者
使用Proxy模式的角色
动态代理?
上面的代理实际上是静态代理(代理模式包含虚拟代理、远程代理、访问代理等等,这里也属于虚拟代理,因为只在真正需要的时候才生成被代理对象实例),静态代理是在编译期即确定了被代理类、代理类,灵活性受限,故有动态代理。
什么是动态代理?
不需要为每一个被代理对象单独创建代理类,在运行时才确定被代理的对象。动态代理包括JDK动态代理和CGLIB动态代理,这里的动态代理指JDK动态代理。
还是使用上面的例子。
JDK动态代理类图
JDK动态代理中,被代理对象依旧要实现接口Playable,但代理对象则不需要,同时,代理对象实例是由ProxyFactory中的getProxyInstance方法返回。Main类在使用动态代理时,需要将一个RealPlayer对象传递给ProxyFactory,用于初始化ProxyFactory的target变量,该变量用于告诉底层需要被代理的对象的类加载器、其实现的接口、实现InvocationHandle接口的类的实例,这里不懂没关系,需要知道的就是:传递了这个target对象,才能生成并返回其相对应的代理对象。
下面看具体代码实现,Playable接口与RealPlayer都和前面相同。主要看代理工厂类与Main类
JDKProxyFactory代理工厂
代理工厂类中,定义了一个target类变量,并在构造器中初始化该变量。最重要的 getProxyInstance 方法中,使用Proxy类的newProxyInstance方法生成代理对象,这里底层使用的是反射的机制实现的。方法需要传入3个参数:
//newProxyInstance方法定义的源码
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
- ClassLoader loader:被代理类的类加载器
- Class<?>[] interfaces:被代理类实现的接口
- InvocationHandler h
传入实现了InvocationHandler 接口的类的实例,本例中使用的是匿名内部类的方式传入。实现该接口需要重写其中的 invoke 方法,在invoke方法中,可以在被代理对象的方法被调用之前与之后做一些操作。
public class JDKProxyFactory {
private static Object target;
public JDKProxyFactory(Object target) {
this.target = target;
}
public static Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用代理对象方法前做处理");
Object res = method.invoke(target, args);
System.out.println("调用代理对象方法后做处理");
return res;
}
}
);
}
}
Main类
主方法类中创建一个 被代理对象 传入代理工厂JDKProxyFactory的构造器并调用 getProxyInstance 方法获取代理对象,再使用代理对象调用 被代理对象 的 setMusicName和playMusic方法,最后一行输出proxyPlayer的类型。
public class Main {
public static void main(String[] args) {
Playable proxyPlayer = (Playable)new JDKProxyFactory(new RealPlayer()).getProxyInstance();
proxyPlayer.setMusicName("圣所");
proxyPlayer.playMusic();
System.out.println(proxyPlayer.getClass());
}
}
输出结果:
可以看到,被代理对象的每个方法被调用的前后,都进行了操作,在这里即输出语句,最后输出的类型:class com.sun.proxy.$Proxy0 可以看到,获取到的的的确确是一个代理对象。
此文为阅读 结城浩 -《图解设计模式》之代理模式有感