目录
定义
代理模式的优缺点
优点
缺点
应用场景
静态代理
动态代理
相关资料
定义
代理模式(Proxy Pattern)是一种结构型设计模式,它的概念很简单,它通过创建一个代理对象来控制对原始对象的访问。代理模式主要涉及两个角色:代理角色和真实角色。代理类负责代理真实类,为真实类提供控制访问的功能,真实类则完成具体的业务逻辑。这样,当我们不方便或者不能直接访问真实对象时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
这样理解其实有点抽象,现实生活中举几个例子 :
-
明星宣传
假如要找一个明星来宣传某一个品牌,那么我们一般不会直接接触到明星,一般是找他的经纪人来商量,这样的话其实经纪人就算是一个代理
-
游戏代练
一般比如玩某些传奇等游戏,里面需要打怪才能升级,这个过程比较繁琐,就会找代练来帮助完成,这样代练就是一个代理
这种还有比如租房中介等等
代理一般分为两类,静态代理和动态代理
-
静态代理
静态代理是指代理类在编译期就已经确定,即需要事先手动编写一个代理类。
-
动态代理
动态代理则是在运行时动态生成代理类 (可以通过继承和实现接口两种方式分别来实现静态和动态,这里就都用接口实现的方式来演示,动态代理方案有两种实现:其一,通过Java本身自带
java.lang.reflect.Proxy
类来实现动态代理功能 。其二,通过额外引入一个开源的高性能代码生成包CGlib来动态生成代理类 。二者底层实现上都应用了反射和操作字节码技术。 )-
JDK动态代理
JDK动态代理是基于接口实现的代理,只能代理实现了接口的类。
-
CGlib
CGlib方式是基于继承实现的代理,它不是指真实类需要继承某个父类,而是生成的代理类作为真实类的子类去代理父类,即代理类继承自真实类。这种方式不需实现接口,可以作为JDK代理方式的补充方案。
-
代理模式的优缺点
优点
-
代理对象可以隐藏原始对象的实现细节,使得客户端无需了解原始对象的具体实现。
-
代理对象可以在原始对象的基础上添加额外的功能,例如缓存、安全验证、日志、权限验证等功能。
-
代理对象可以控制对原始对象的访问,保护原始对象不被非法访问。
-
代理对象可以在客户端和原始对象之间起到中介作用,使得客户端与原始对象之间的耦合度降低。
缺点
-
引入代理类会增加系统的复杂性,增加了学习和理解的成本。
-
由于增加了代理层,导致请求处理速度变慢。
应用场景
-
日志记录代理:通过代理模式,我们可以在真实对象的方法执行前后进行日志记录,以实现日志记录、调试和性能监测等功能。
-
缓存代理:代理模式可以用于实现对象的缓存,当客户端请求某个对象时,代理对象先检查缓存中是否存在该对象,如果存在则直接返回,否则创建新对象并缓存起来,从而提高系统性能。
-
权限校验:某个用户是否具有访问某个特定功能的权限,可以在请求处理前让代理对象先检查一下是否拥有该方法的权限,没有则拒绝,有则可以访问
下面就通过传奇游戏代练的场景来实现下没有代理模式是怎么样的,以及有了代理模式发生了哪些变化
我们想一下传奇游戏一般玩家都会干些什么,登录,打怪,升级 , 将这些事情抽象成一个游戏玩家接口 (IGamePlayer):
public interface IGamePlayer {
/**
* 登录
* @param userName
* @param password
*/
void login(String userName,String password);
/**
* 打怪
*/
void killBoss();
/**
* 升级
*/
void upgrade();
}
然后创建一个普通的玩家 (GamePlayer) 真实类
public class GamePlayer implements IGamePlayer {
//用户名
private String name;
public GamePlayer(String name) {
this.name = name;
}
public GamePlayer() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void login(String userName, String password) {
System.out.println("登录名为:"+userName+"的用户登录成功");
}
public void killBoss() {
System.out.println(this.name+"在打怪");
}
public void upgrade() {
System.out.println(this.name+"升级成功!");
}
}
此时类图结构如下 :
这是没有代理模式的情况
public class Main {
public static void main(String[] args) {
//原始,zhangsan只能自己打游戏
GamePlayer gamePlayer = new GamePlayer("zhangsan");
gamePlayer.login("zhangsan","123");
gamePlayer.killBoss();
gamePlayer.upgrade();
}
}
结果:
登录名为:zhangsan的用户登录成功
zhangsan在打怪
zhangsan升级成功!
静态代理
我们此时假如叫了一个代练,让代练除了帮我打升级以外,还得在开始代练前后通知我一下,让我别挤你的账号,创建一个代理类,继承游戏玩家接口GamePlayerProxy (代理类)
public class GamePlayerProxy implements IGamePlayer {
//被代理的目标对象
private GamePlayer gamePlayer;
//通过构造传入进来
public GamePlayerProxy(GamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
public GamePlayerProxy() {
}
public GamePlayer getGamePlayer() {
return gamePlayer;
}
public void setGamePlayer(GamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
//对下面方法进行代理
public void login(String userName, String password) {
System.out.println("通知用户代练,login开始了");
this.gamePlayer.login(userName,password);
System.out.println("通知用户代练,login结束了");
}
public void killBoss() {
System.out.println("通知用户,代练killBoss开始了");
this.gamePlayer.killBoss();
System.out.println("通知用户,代练killBoss结束了");
}
public void upgrade() {
System.out.println("通知用户,代练upgrade开始了");
this.gamePlayer.upgrade();
System.out.println("通知用户,代练upgrade结束了");
}
}
此时类图结构 :
这样的话我自己就其实不需要打游戏了
public class Main {
public static void main(String[] args) {
//原始,zhangsan只能自己打游戏
// GamePlayer gamePlayer = new GamePlayer("zhangsan");
// gamePlayer.login("zhangsan","123");
// gamePlayer.killBoss();
// gamePlayer.upgrade();
//zhangsan找了代练
GamePlayer gamePlayer = new GamePlayer("zhangsan");
GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(gamePlayer);
gamePlayerProxy.login("zhangsan","123");
gamePlayerProxy.killBoss();
gamePlayerProxy.upgrade();
}
}
结果 :
通知用户代练,login开始了
登录名为:zhangsan的用户登录成功
通知用户代练,login结束了
通知用户,代练killBoss开始了
zhangsan在打怪
通知用户,代练killBoss结束了
通知用户,代练upgrade开始了
zhangsan升级成功!
通知用户,代练upgrade结束了
这其实就是静态代理,创建一个类,实现指定目标对象的接口,然后重写方法,内部会保存目标对象,然后重写的方法中会去调用目标对象的方法,前后可以干些非主业务的事情
静态代理的缺点其实比较明显 , 主要有以下几点 :
-
代理对象的一个接口只服务于一种类型的对象。如果要代理的方法很多,势必要为每一种方法都进行代理,这在程序规模稍大时就无法胜任了。
-
如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,这增加了代码维护的复杂度。
-
代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法,这样就出现了大量的代码重复。
下面我们来看下动态代理是如何解决这些问题的
动态代理
JDK动态代理是Java标准库中提供的一种代理方式,它可以在运行时动态生成一个代理对象,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。JDK动态代理通过反射机制实现代理功能,其原理分为以下几个步骤:
-
创建InvocationHandler接口的代理类工厂:在调用Proxy类的静态方法newProxyInstance时,会动态生成一个代理类。该代理类实现了目标接口,并且持有一个InvocationHandler类型的引用。
-
InvocationHandler接口:InvocationHandler是一个接口,它只有一个方法invoke。在代理对象的方法被调用时,JVM会自动调用代理类的invoke方法,并将被调用的方法名、参数等信息传递给该方法。
-
调用代理对象的方法:当代理对象的方法被调用时,JVM会自动调用代理类的invoke方法。在invoke方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等。
-
invoke方法调用:在invoke方法中,会再通过反射机制调用目标对象的方法,并返回方法的返回值。在调用目标对象的方法前后,可以执行额外的逻辑。
代理类工厂 :
public class EnhanceHandler implements InvocationHandler {
//目标对象
private Object obj;
public EnhanceHandler(Object obj){
this.obj = obj;
}
//获取jvm在内存中生成的代理对象
public Object getProxy() {
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
System.out.println("通知用户,代练" + methodName +"开始了");
method.invoke(obj,args);
System.out.println("通知用户,代练" + methodName + "结束了");
return null;
}
}
public class Main {
public static void main(String[] args) {
EnhanceHandler enhanceHandler = new EnhanceHandler(new GamePlayer("zhangsan"));
IGamePlayer proxy = (IGamePlayer) enhanceHandler.getProxy();
proxy.login("zhangsan","123");
proxy.killBoss();
proxy.upgrade();
}
}
结果如下 :
通知用户,代练login开始了
登录名为:zhangsan的用户登录成功
通知用户,代练login结束了
通知用户,代练killBoss开始了
zhangsan在打怪
通知用户,代练killBoss结束了
通知用户,代练upgrade开始了
zhangsan升级成功!
通知用户,代练upgrade结束了
此时类图结构如下 :
其实这里 Proxy会在运行时动态生成IGamePlayer的实现类然后重写方法,然后我们调用代理对象方法的时候就会先到继承了InvocationHandler接口对象的invoke方法中,内部会再次调用真实对象的方法
流程图 :
PS : 默认情况下JVM是不保存动态创建代理类字节码对象的,可以在main方法中配置代理参数让字节码保留
//JDK8之前
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK8之后
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
执行完之后,会在项目根目录生成代理类字节码对象。
JVM运行时生成的代理类对象 :
public final class $Proxy0 extends Proxy implements IGamePlayer {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m5;
private static Method m4;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void upgrade() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void killBoss() throws {
try {
super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void login(String var1, String var2) throws {
try {
super.h.invoke(this, m4, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.changjunkai.designmode.proxyPattern.service.IGamePlayer").getMethod("upgrade");
m5 = Class.forName("com.changjunkai.designmode.proxyPattern.service.IGamePlayer").getMethod("killBoss");
m4 = Class.forName("com.changjunkai.designmode.proxyPattern.service.IGamePlayer").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
相关资料
设计模式之禅第二版