代理模式顾名思义就是进行代理,简单来说就是经纪人,他直接与你沟通,并帮助做更多的事情。
在程序中,代理模式那可谓是非常重要,像Spring的aop就是动态代理,而且很多框架中都是用到了代理模式。
代理模式在我们不改变原有代码的基础上对某一个方法进行增强,这种增强可以是提前编写好的代码,也可以是自动编写的代码。这就分为静态代理和动态代理。
静态代理
静态代理是显式的帮助我们对目标类进行增强。
我们定义一个场景:12306提供最基本的卖票功能,但也有很多软件也卖票,例如美团、携程等。其实在第三方平台买票实际也是要登陆12306账号的,不过第三方会提供一些额外的功能帮你买票,比如说加速包抢票等。而这些额外的服务其实就是在12306买票的基础上增加了额外的功能,简单理解就是进行了加工,这其实就是一种代理。
/** * 1.定义卖票接口 */ public interface Tickets { void sell(); }
/** * 2、12306类 */ public class Demo12306 implements Tickets { @Override public void sell() { System.out.println("卖票"); } }
/** * 3.美团类里包含了12306类,并且在12306类的基础上增加了抢票功能 */ public class MeiTuan implements Tickets { private Tickets tickets; MeiTuan(Tickets tickets){ this.tickets = tickets; } @Override public void sell() { System.out.println("加速包开始抢票"); //这里是12306原本的卖票 tickets.sell(); System.out.println("加速包抢票成功"); } }
public class Test { public static void main(String[] args) { //创建原始的12306类 Tickets demo12306 = new Demo12306(); //美团对12306进行代理,增加额外的抢票功能 Tickets meiTuan = new MeiTuan(demo12306); /** * 打印结果: * 加速包开始抢票 * 卖票 * 加速包抢票成功 */ meiTuan.sell(); } }
这种方式虽然在不改变原本类的基础上增加了功能,但是我们如果每有代理需求的时候,就要一直创建代理类,而且很不灵活,并且在jvm运行之前.class文件就已经生成了。
动态代理
动态代理相比较于静态代理区别在于:我们不用设计一个代理类来具体的来代理某一个对象,而是程序运行的时候在jvm中帮我们生成代理类。
而代理和被代理之前有一个包含的关系,代理要从被代理那里获取到所有方法进行代理。这样以来就需要被代理类有一个统一的对外服务标准,这个标准就是接口和抽象类。
对于动态代理我们有两种实现方式,一个是基于接口的实现、另一个是基于抽象类的实现。
jdk实现动态代理
jdk本身就帮我们实现了动态代理,这种代理是基于接口方式进行实现的。
还是之前卖票的例子,但是这一次是用动态代理方式进行实现。
美团并不去实现卖票接口了,而是实现 InvocationHandler
接口,在创建代理类的时候就不是直接调用方法了(当然也调用不了,没有实现卖票接口了),这个时候使用 Proxy.newProxyInstance()
来进行创建。我们先上代码,再来进行解释。
/** * 1.定义卖票接口 */ public interface Tickets { void sell(); }
/** * 2、12306类 */ public class Demo12306 implements Tickets { @Override public void sell() { System.out.println("卖票"); } }
前两步其实都是一样的
/** * 3.美团类里包含了12306类,并且在12306类的基础上增加了抢票功能 * 这里实现InvocationHandler接口 * 本类不是真正的代理类了 */ public class MeiTuan implements InvocationHandler { private Tickets tickets; MeiTuan(Tickets tickets){ this.tickets = tickets; } /*** * 该方法是代理类在执行时候调用的方法 * @param proxy 代理类本身(这里就是美团) * @param method 代理类调用的具体方法 (我们调用的卖票所以这里就是卖票方法、通俗点就是你调用什么方法,这里就是什么方法) * @param args 方法参数 上面方法对应的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("加速包开始抢票"); //这里是12306原本的卖票,但是是使用反射进行调用 Object invoke = method.invoke(tickets, args); System.out.println("加速包抢票成功"); return invoke; } }
从这一步开始就一下变的面目全非了,代理类去实现 InvocationHandler
接口,重写 invoke
方法。
public class Test { public static void main(String[] args) { //创建原始的12306类和代理类 Tickets demo12306 = new Demo12306(); MeiTuan proxy = new MeiTuan(demo12306); /** * 美团对12306进行代理,增加额外的抢票功能 * 这里通过Proxy.newProxyInstance方法在运行的时候动态创建代理类 * 这里有三个参数 * ClassLoader loader, 类加载器,这里主要是选择代理的类加载器 * lass<?>[] interfaces, 被代理类的所有接口 * InvocationHandler h 代理类 */ Tickets meiTuan = (Tickets) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), demo12306.getClass().getInterfaces(), proxy); /** * 打印结果: * 加速包开始抢票 * 卖票 * 加速包抢票成功 */ meiTuan.sell(); } }
最终代理对象是通过 Proxy.newProxyInstance 创建得出的。
其实到这里你只要跟着案例把代码敲出来,就已经实现了动态代理。但是你肯定会有个疑问以下为什么用到这个方法,并且参数又是什么意思?
invoke(Object proxy, Method method, Object[] args)
首先是代理类重写的invoke方法,这里有三个参数。我们回顾一下静态代理,静态代理中我们显式的实现了所有被代理类实现的接口,所以我们有被代理类的所有方法,可能是多个。那么在增强功能的时候,我们就需要每一个方法进行增强,耗时耗力。但是我们把所有方法抽象成Method
,在Method前后进行增强不就同时增强了所有方法吗。
这里的第二个和第三个参数就比较明朗了,既然我把所有方法抽象成Method
,那么自然需要反射调用具体的方法。
那么第一个参数到底是什么呢?这个其实是代理类本身,它在调用不同的方法的时候把代理类本身也就是this传递了进来,为什么要传递代理类呢。
- 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())。
- 可以将代理对象返回以进行连续调用
当接口中方法返回值是接口类型的时候,可以在invoke方法中返回代理对象,这样在使用代理对象的时候可以进行链式调用,具体实例自行尝试。
Proxy.newProxyInstance(proxy.getClass().getClassLoader(), demo12306.getClass().getInterfaces(), proxy);
这里第一个参数就是类加载器,他的作用是用来加载代理对象的,因为我们的代理类是在内存中生成的,那么如果我们在内存中生成用了额外的加密手段,那么我们就要自定义类加载器来进行加载。但是这里jdk就是正常的生成内存中的.class文件,所以我们用正常的application加载器就可以了。第二个参数就是被代理类的所有接口,第三个参数就是InvocationHandler
,其实就是代理类在执行对应方法的时候要调用的方法。
手撕jdk动态代理源码
这里介绍几个关键的方法
首先是Proxy类的719行,查找或生成代理类
然后是Proxy类的639行,这里是具体在内存中生成.class的方法
进入到该方法后第二行 final byte[] classFile = gen.generateClassFile();
点击该方法进入
这里是ProxyGenerator类的第427行,这里详细介绍了实现步骤仔细看注释
第三步就开始在jvm中拼接代理类,这时候是.class文件,最后会使用类加载器把.class文件加载到jvm中就获得了Class对象
然后我们在回到Proxy类的719行往下看,这不就是反射生成对象吗。所以我们就拿到了代理对象。
这里.class文件由于在jvm中我们看不到,这里其实可以把内存中的.class输出到文件中然后进行反编译。
public class Test2 { public static void main(String[] args) throws IOException { byte[] data = ProxyGenerator.generateProxyClass("$Proxy0.class", new Class[]{Tickets.class}); FileOutputStream out = new FileOutputStream("$Proxy0.class"); out.write(data); out.close(); } }
这时候idea目录下已经多出了这个文件
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package $Proxy0; import com.cstor.设计模式.代理模式.静态.Tickets; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class class extends Proxy implements Tickets { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public class(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 sell() throws { try { super.h.invoke(this, m3, (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); } } 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.cstor.设计模式.代理模式.静态.Tickets").getMethod("sell"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
细心的朋友可以看到这里真实的代理类里面的属性是每个方法,不过在原有接口的方法上多出了hashCode、toString、equals。然后在方法中调用的InvocationHandler
中的invoke方法,传递对应的Method