一、代理模式介绍
代理模式,非常容易理解,一眼看过去大家都懂,字面意思,如果不懂稍微琢磨琢磨就懂了,我在这里举几个栗子🌰 ,以便更容易理解。
- 贾乃亮作为一个明星,为了更专注于自己的工作,他找了一个经纪人,帮他约综艺、约电视剧、约电影等等。
- 英雄联盟打的太菜了,但是为了冲到更高的段位,在别人面前装装13,于是找了一个代练帮忙上分。
- 背井离乡,来到一个陌生的城市打拼,不知道住在哪里怎么办?我们可以通过中介更快的找到心仪的住所。
- 春运的票实在太难抢,我们只能使用抢票软件帮我们抢票。
可以看到,在生活中,代理无处不在,无论是经纪人、游戏代练、房产中介、抢票软件,都是帮我们做事,对我们的能力进行了扩展。
那么在Java中使用代理模式,通过代理对象访问目标对象,从而在不改变目标对象原有代码的基础上,进行功能的扩展和增强。
目前在Java中分为三种代理模式:静态代理、jdk动态代理、cglib动态代理。
二、静态代理
静态代理,需要代理对象和目标对象实现同一个接口。
现在市面上的游戏很多,我们创建一个接口,写上现在一直很火的一款游戏,英雄联盟
public interface Play {
public void lol();
}
有一个对象,他叫 “有趣的灵魂200斤”,是个肥宅,名如其人对吧,他有一个爱好,就是玩、娱乐,我们让肥宅小兄弟实现一下这个接口,看看他是怎么玩这个游戏的。
public class FeiZhai implements Play {
public void lol() {
System.out.println("B键已扣,不死不休!");
}
}
有一天我们的肥宅小兄弟,刚开了新的一局英雄联盟排位赛,突然肚子一阵作响,他看了看时间,于是拿起手机打开饿不饿APP订了个餐,然后继续游戏。
那么我们的肥宅小兄弟为什么在网上订餐呢,因为他不会做饭,所以只能在网上订餐,而且商家做好餐,由我们的外卖小哥送过来,就可以吃了,很方便,主要是不耽误玩游戏。
/**
* 饿不饿App-肥宅小兄弟
*/
public class EbueProxy implements Play {
//声明目标对象-为肥宅小兄弟服务
private FeiZhai feiZhai;
//通过构造方法,获取目标对象,进行赋值
public EbueProxy(Play play) {
this.feiZhai = (FeiZhai) play;
}
//对原来的方法进行扩展
public void lol() {
System.out.println("商家接到订单,开始做饭");
System.out.println("饭做完了,外卖小哥取到餐");
//调用目标对象的方法
//在不影响肥宅小兄弟玩LOL的情况下,进行功能的拓展和增强。
feiZhai.lol();
System.out.println("外卖小哥进行配送");
System.out.println("叮咚!您好,您的餐,请慢用!");
}
}
我们的肥宅小兄弟,为了不耽误自己玩游戏,委托饿不饿APP送餐上门,这就是一种代理模式,让我们运行看一下结果。
public class main {
public static void main(String[] args) {
Play feiZhai = new FeiZhai();
EbueProxy ebueProxy = new EbueProxy(feiZhai);
ebueProxy.lol();
}
}
商家接到订单,开始做饭
饭做完了,外卖小哥取到餐
B键已扣,不死不休!
外卖小哥进行配送
叮咚!您好,您的餐,请慢用!
三、jdk 动态代理
又是充满希望的一天,今天我们的肥宅小兄弟不想玩LOL了,他熟练的拿起手机,打开和平精英,准备吃(kuai)鸡(di)之旅。
我们给接口类加一个方法
public interface Play {
public void lol();
public void chiJi();
}
再具体实现一下
public class FeiZhai implements Play {
public void lol() {
System.out.println("B键已扣,不死不休!");
}
public void chiJi() {
System.out.println("人体描边说的就是在下了!");
}
}
到饭点了,又是和昨天一样的情况,我们再写一套代码,委托饿不饿APP帮我们送下餐
/**
* 饿不饿App
*/
public class EbueProxy implements Play {
//声明目标对象-为肥宅小兄弟服务
private FeiZhai feiZhai;
//通过构造方法,获取目标对象,进行赋值
public EbueProxy(Play play){
this.feiZhai = (FeiZhai) play;
}
//对原来的方法进行扩展
public void lol() {
System.out.println("商家接到订单,开始做饭");
System.out.println("饭做完了,外卖小哥取到餐");
//调用目标对象的方法
//在不影响肥宅小兄弟玩LOL的情况下,进行功能的拓展和增强。
feiZhai.lol();
System.out.println("外卖小哥进行配送");
System.out.println("叮咚!您好,您的餐,请慢用!");
}
//对原来的方法进行扩展
public void chiJi() {
System.out.println("商家接到订单,开始做饭");
System.out.println("饭做完了,外卖小哥取到餐");
//调用目标对象的方法
//在不影响肥宅小兄弟玩吃鸡的情况下,进行功能的拓展和增强。
feiZhai.chiJi();
System.out.println("外卖小哥进行配送");
System.out.println("叮咚!您好,您的餐,请慢用!");
}
}
看到这里的小伙伴们,应该都发现问题了,如果肥宅小兄弟每天换个游戏,那我们岂不是要累死了,而且现在还只是1个肥宅小兄弟,如果是2个、3个、100个呢,难道我们要创建不同的 EbueProxy 代理类么?
要知道现在咱们代码中的 EbueProxy 类,只为 FeiZhai 这个类提供代理服务,相当于定制版,如果我想为FeiZhai1、FeiZhai2提供代理服务,我还要再写2个代理类,如果这个时候Play接口新加了几个方法,我们又要去维护实现了Play接口的这些类,很不方便。
本着最少的代码干最多的事这种原则,我们不能接受这样的结果。
这个时候我们就要使用 jdk动态代理 了,我们创建一个类 EbueDynamicProxy,实现 InvocationHandler 接口,重写 invoke 方法。
/**
* 饿不饿App-动态代理
*/
public class EbueDynamicProxy implements InvocationHandler {
//注意这里,我们换成了Object,意味着我们可以接受不同的目标对象
private Object object;
//通过构造方法,获取目标对象,进行赋值
public EbueDynamicProxy(Object object){
this.object = object;
}
/**
*
* 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,
* 当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
* InvocationHandler这个接口的唯一一个方法 invoke 方法:
* 该方法接收3个参数:
* proxy: 指代我们所代理的那个目标对象
* method: 指代的是我们所要调用目标对象的某个方法的Method对象
* args: 指代的是调用目标对象某个方法时接受的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("商家接到订单,开始做饭");
System.out.println("饭做完了,外卖小哥取到餐");
Object obj = method.invoke(object, args);
System.out.println("外卖小哥进行配送");
System.out.println("叮咚!您好,肥先生的餐,请慢用!\n");
return obj;
}
}
静态代理和jdk动态代理最大的区别就是,jdk动态代理是jvm帮我们生成了代理类。
至于jvm怎么生成的我们目前不需要去操心,我们看看我们怎么触发去让jvm生成。
public class main {
public static void main(String[] args) {
//创建一个目标对象的实例
Play feizhai = new FeiZhai();
//创建一个与目标对象相关联的InvocationHandler
InvocationHandler ebueDynamicProxy = new EbueDynamicProxy(feizhai);
//获取目标对象的类加载器
ClassLoader classLoader = feizhai.getClass().getClassLoader();
//获取目标对象的所有接口
Class[] interfaces = feizhai.getClass().getInterfaces();
//Proxy.newProxyInstance:返回代理类的一个实例,返回后的代理类可以当作被代理类使用
Play feiZhaiProxy = (Play) Proxy.newProxyInstance(classLoader, interfaces, ebueDynamicProxy);
//英雄联盟
feiZhaiProxy.lol();
//吃鸡
feiZhaiProxy.chiJi();
}
}
运行一下看看输出
商家接到订单,开始做饭
饭做完了,外卖小哥取到餐
B键已扣,不死不休!
外卖小哥进行配送
叮咚!您好,您的餐,请慢用!
商家接到订单,开始做饭
饭做完了,外卖小哥取到餐
人体描边说的就是在下了!
外卖小哥进行配送
叮咚!您好,您的餐,请慢用!
是不是很简单,接下来我们分析一下原理,知道怎么做还不够,还得知道为什么能这么做。
通过静态代理,我们很明显可以看到,动态代理是由静态代理演化而来,之前我们手写代理类,有几个目标对象就写几个代理类,目标对象里有几个方法,代理类里就写几个方法,现在换成jdk动态代理,我们就靠jvm来动态生成代理类,我们只要需要实现 InvocationHandler 接口,重写 invoke 方法,添加上需要扩展的功能代码,然后交给jvm就可以了,jvm会帮我们在程序运行时生成代理类,代理类的名称为$Proxy0、$Proxy1、$Proxy2 …以此类推。
这个时候我们想看看jvm生成的代理类是什么样的怎么办?我们在main方法里面加上这样一行代码,这样jvm生成的代理类就会出现在项目里的com.sun.proxy这个目录下面了。
public class main {
public static void main(String[] args) {
//这行代码的意思是将JDK动态代理生成的class文件保存到本地
//作者本地jdk1.8亲测可用,有人说新版本的jdk用下面一行好使,不知道是多新的版本,如果有人用了发现没生成,可以换下面这行代码试试!
//System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//创建一个目标对象的实例
Play feizhai = new FeiZhai();
//创建一个与目标对象相关联的InvocationHandler
InvocationHandler ebueDynamicProxy = new EbueDynamicProxy(feizhai);
//获取目标对象的类加载器
ClassLoader classLoader = feizhai.getClass().getClassLoader();
//获取目标对象的所有接口
Class[] interfaces = feizhai.getClass().getInterfaces();
//Proxy.newProxyInstance:返回代理类的一个实例,返回后的代理类可以当作被代理类使用
Play feiZhaiProxy = (Play) Proxy.newProxyInstance(classLoader, interfaces, ebueDynamicProxy);
//英雄联盟
feiZhaiProxy.lol();
//吃鸡
feiZhaiProxy.chiJi();
}
}
我们运行一下,果然生成了,文件名也是$Proxy开头
我们再看看class文件里面的代码是什么样的
package com.sun.proxy;
import com.test.proxy.Play;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Play {
private static Method m1;
private static Method m2;
private static Method m0;
private static Method m3;
private static Method m4;
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 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);
}
}
public final void chiJi() 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 lol() throws {
try {
super.h.invoke(this, m4, (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");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.test.proxy.Play").getMethod("chiJi");
m4 = Class.forName("com.test.proxy.Play").getMethod("lol");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
我们看到在这个类里面,写了一个静态代码块,这里的m1、m2、m3等等,都是使用了反射找到了对应的Method对象。
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.test.proxy.Play").getMethod("lol");
m4 = Class.forName("com.test.proxy.Play").getMethod("chiJi");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
然后我们再找到我们写的lol方法,我们拿这个进行举例说明。
public final void lol() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
可以看到代码很简单,主要就是这么一行代码
super.h.invoke(this, m3, (Object[])null);
这行代码的意思就是调用当前类的父类的h变量的invoke方法。这么读发现有点绕是不是,没事,我们来剖析一下看看当前类的父类是谁?当前类的父类的h变量又是什么?调用这个invoke方法又有什么用?来上代码。
import com.test.proxy.Play;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Play
从上面这段代码我们可以看到,$Proxy0代理类继承了Proxy类,并且实现了我们写的Play接口,这下明白了,当前类的父类就是Proxy类。
public class Proxy implements java.io.Serializable {
private static final long serialVersionUID = -2222568056686623797L;
private static final Class<?>[] constructorParams = { InvocationHandler.class };
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
//看这里
protected InvocationHandler h;
}
再看上面这段代码,h变量就是InvocationHandler类,那么super.h.invoke方法,就相当于是调用了proxy的h变量的invoke方法,还记得invoke方法是什么吗,就是我们写的EbueDynamicProxy类里面的invoke方法,执行的就是这个方法。至于什么时候把EbueDynamicProxy类赋值给了h变量,我们在main方法里不是写了Proxy.newProxyInstance了,就是在那个时候,把EbueDynamicProxy传过去的。
接下来我们来粗略看一下cglib动态代理。
如果你使用了Spring框架,就不用单独引入cglib了,因为Spring集成了cglib。
接下来我们创建一个普通的类,写上一个方法。
public class HelloFeiZhai {
public void hello(){
System.out.println("你好,肥宅!");
}
}
然后我们开始为这个类创建动态代理,首先要创建一个类,实现MethodInterceptor接口,并且重写intercept方法。
public class CglibDynamicProxy implements MethodInterceptor {
/**
* 增强代码
* @param object 被代理的对象
* @param method 代理的方法
* @param objects 方法参数
* @param methodProxy cglib方法代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("你好,我叫 有趣的灵魂200斤");
Object obj = methodProxy.invokeSuper(object, objects);
System.out.println("我好你******");
return obj;
}
}
然后看看main方法
public static void main(String[] args) {
CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy();
//Enhancer是cglib的字节码增强器,用来创建动态代理类
Enhancer enhancer = new Enhancer();
//指定要代理的业务类(即:为要生成的代理类指定父类)
enhancer.setSuperclass(HelloFeiZhai.class);
//设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现MethodInterceptor接口的intercept()方法进行拦截
enhancer.setCallback(cglibDynamicProxy);
// 创建动态代理类对象并返回
HelloFeiZhai helloFeiZhai = (HelloFeiZhai)enhancer.create();
// 调用
helloFeiZhai.hello();
}
运行结果:
你好,我叫 有趣的灵魂200斤
你好,肥宅!
我好你*****
四、cglib 动态代理
cglib的源码确实比较复杂,我自己还有好多地方不明白,就不拿出来误导大家了,我粗略的说一下原理:通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
五、总结
jdk动态代理在老的java版本中,效率不尽人意,是比cglib要慢的,但是在jdk1.8版本,据大佬说,jdk动态代理已经是比cglib要快了。
在这篇文章中我们讲了java设计模式之一的《代理模式》,介绍了现在java中的三种代理模式(静态代理、jdk动态代理、cglib动态代理),而且分别演示了三种代理模式的使用方法、说明了它们之间的关系和区别,以及优缺点。
如果写的有什么问题,欢迎大家在底下评论指正,我会及时采纳和修改,感谢!