代理模式在我们平常应用中极为常见,尤其是对于JavaWeb开发来说,使用的Spring框架中AOP(面向切面编程)就是使用的代理模式中的动态代理。
代理模式的定义
对其他对象提供一种代理以控制对这个对象的访问。
听起来有点玄乎,换个栗子来将,平常在生活中打官司,你会请名律师,律师的任务就是帮你处理中间的所有事,而解放你的双手,律师就相当于代理,通过他我们可以完成我们想要完成的事。
栗子
首先先通过一个简单的栗子来了解下代理模式:
定义一个玩家接口,这个玩家可以打怪升级以及打Boss。
public interface Player {
//打怪升级
void upgrade();
//打Boss
void killBoss();
}
实现这个玩家接口,构造器中传入玩家姓名。
public class GamePlayer implements Player {
private String name = "";
public GamePlayer(String name) {
this.name = name;
}
@Override
public void upgrade() {
System.out.println(name + "升级了");
}
@Override
public void killBoss() {
System.out.println(name + "打死了大Bose");
}
}
接下来就是最重要的代理类了,在构造器中传入一个玩家对象,在代理类的方法中操控的就是这个玩家类的方法。
public class GamePlayerProxy implements Player{
private Player gamePlayer;
public GamePlayerProxy(Player gamePlayer) {
this.gamePlayer = gamePlayer;
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
}
写个场景类来测试一下:
public class Client {
public static void main(String[] args) {
//创建一个对象
Player gamePlayer = new GamePlayer("张三");
//创建一个代理类,并传入对象
Player gamePlayerProxy = new GamePlayerProxy(gamePlayer);
//操控代理类的方法实则上是操控的是对象的该方法
gamePlayerProxy.killBoss();
}
}
上面就是一个简单的代理模式,通过代理类来操控对象的方法。
代理模式的应用
优点:
- 职责清晰。真实对象只需要实现实际的业务逻辑即可。对象的使用通过代理类来完成。
- 高扩展性。具体的对象随时会发生改变,但是我们使用的是他的代理类,所有不管他怎么改变也逃不出我们的手掌心。
使用场景:
代理模式接触最多的就是AOP面向切面编程了,一般日志、权限、事务都可以用代理模式来完成。比如日志的实现,在打Boss之前需要记录一下打Boss的时间,因此我们只需要在代理类的打Boss方法中调用真实对象打Boss前执行打印时间即可,
public void killBoss() {
System.out.println("打Boss的时间:" + System.currentTimeMillis());
this.gamePlayer.killBoss();
}
代理模式的扩展
代理模式有几种扩展,比如普通代理、强制代理和动态代理。
普通代理:需要用户手动创建代理对象,然后通过自己创建的代理对象来操控真实对象。
强制代理:代理对象由真实对象产生,用户从真实对象处获得代理对象,通过获得的代理对真实对象进行操作。
普通代理
我们还是通过玩家这个接口来完成普通代理的实现
普通代理要求客户端只能访问代理对象,而不能访问真实对象,所以我们只需要对上面栗子的代理类做出细微改变就能实现普通代理。
public class GamePlayerProxy implements Player{
private Player gamePlayer;
//通过代理类来创建真实对象
public GamePlayerProxy(String name) {
if(name != null && !"".equals(name)){
gamePlayer = new GamePlayer(name);
}
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
}
通过在代理类的构造函数中通过传入的name值来创建真实对象,这样可以防止客户端直接对真实对象进行操作,客户端只能操控代理对象来实现对真实对象方法的操控。
强制代理
强制代理要求,代理类需要由真实对象产生。这样客户端是不知道代理的存在的,因此代理对于客户端来说是透明的。
我们先稍微修改下玩家类
public interface Player {
void upgrade();
void killBoss();
//增加一个获得玩家代理类的方法
GamePlayerProxy getPlayerProxy();
}
玩家接口修改了,实现类也需要做出一点修改
public class GamePlayer implements Player {
private String name = "";
private GamePlayerProxy playerProxy = null;
@Override
public GamePlayerProxy getPlayerProxy(){
playerProxy = new GamePlayerProxy(this);
return this.playerProxy;
}
public GamePlayer(String name) {
this.name = name;
}
@Override
public void upgrade() {
if(isProxy()){
System.out.println(name + "升级了");
} else {
System.out.println("请使用指定的代理访问");
}
}
@Override
public void killBoss() {
if(isProxy()){
System.out.println(name + "打死了大Bose");
} else {
System.out.println("请使用指定的代理访问");
}
}
//判断是否代理类调用
private Boolean isProxy(){
if(this.playerProxy == null){
return false;
} else {
return true;
}
}
}
其中,必须要调用玩家实现类的getPlayerProxy()
方法才会创建一个代理类,并返回这个代理类,在执行每个方法前,也需要通过isProxy()
方法判断调用方法的对象是否是代理类。
运行结果如下:
public class Client {
public static void main(String[] args) {
Player player = new GamePlayer("张三");
System.out.println("直接通过调用玩家类杀Boss=====================");
player.killBoss();
GamePlayerProxy gamePlayerProxy = player.getPlayerProxy();
System.out.println("通过代理类杀Boss=====================");
gamePlayerProxy.killBoss();
}
}
//output
直接通过调用玩家类杀Boss=====================
请使用指定的代理访问
通过代理类杀Boss=====================
张三打死了大Bose
如果我们试图直接通过对象执行方法,会被判断没有使用代理类,因此无法输出正确结果。通过这种代理模式,我们也可以实现权限的功能,好了,就点到这吧,接下来介绍最后一种,动态代理。
动态代理
动态代理是指程序在实现阶段不用关心代理谁,而是在运行时才指定代理哪一个对象。AOP就采用JDK的动态代理机制(当然有时也是CGLib的代理模式)。
为了实现动态代理,我们需要使用到JDK提供的动态代理接口InvocationHandler
,并覆写里面的invoke()
方法。
还是用玩家一个接口为例
//玩家类接口
public interface Player {
void upgrade();
void killBoss();
}
//玩家类接口的实现
public class GamePlayer implements Player {
private String name = "";
public GamePlayer(String name) {
this.name = name;
}
@Override
public void upgrade() {
System.out.println(name + "升级了");
}
@Override
public void killBoss() {
System.out.println(name + "打死了大Bose");
}
}
通过实现JDK的InvocationHandler
接口从而获得代理,这里我们覆写了该接口的invoke
方法,该方法中需要执行被代理对象的方法
public class InvocationHandlerImpl implements InvocationHandler {
//被代理的对象
private Player gamePlayer = null;
public InvocationHandlerImpl(Player player) {
this.gamePlayer = player;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在调用代理类方法之前我想干点啥");
//执行被代理对象的方法
Object obj = method.invoke(this.gamePlayer, args);
System.out.println("在调用代理类方法之后我打印了");
return obj;
}
}
再来看看场景类中如何实现我们的动态代理
public class Client {
public static void main(String[] args) {
Player player = new GamePlayer("张三");
InvocationHandler handler = new InvocationHandlerImpl(player);
ClassLoader cl = player.getClass().getClassLoader();
/**
* 通过Proxy的newProxyInstance方法来创建代理对象
* 参数1:获得要代理的类的类加载器
* 参数2:代理对象的所有接口方法
* 参数3:实现了InvocationHandler的动态代理类
*/
Player proxy = (Player) Proxy.newProxyInstance(cl, new Class[]{Player.class}, handler);
proxy.killBoss();
}
}
在InvocationHandlerImpl
中我们已经告诉代理类在调用对象的方法时应该怎么办了,也就是我们已经将GamePlayer
的所有方法代理给了InvocationHandlerImpl
类中的invoke
方法来执行,在程序运行时才会指定代理哪个类。
通过Proxy
类的静态方法newProxyInstance
可以获得代理类,这里需要传入三个参数,被代理类的类加载器、被代理对象的所有接口方法以及实现了InvocationHandler的动态代理类。
动态代理测试:
在调用代理类方法之前我想干点啥
张三打死了大Bose
在调用代理类方法之后我打印了
我们成功的通过代理执行了对象的打Boss方法,可是,咦,爪子还多打印了些东西呢,仔细看看会发现,在invoke
中调用方法对象的invoke
方法前后有一些额外的输出,通过这个,我们也可以简单的实现日志的效果。
总结
从动态代理看出,要想实现它只需要一个接口,以及实现类即可。代理模式应用很广泛,大到一个系统框架,小到代码片段、事务处理。在平常进行调试时,只要看到类似$Proxy0
这样的结果,就应该知道这是一个动态代理了。