设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理) 1

简介: 设计模式之代理模式(jdk和cglib、手撕源码、自创动态代理)

代理模式顾名思义就是进行代理,简单来说就是经纪人,他直接与你沟通,并帮助做更多的事情。

在程序中,代理模式那可谓是非常重要,像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传递了进来,为什么要传递代理类呢。

  1. 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())。
  2. 可以将代理对象返回以进行连续调用

当接口中方法返回值是接口类型的时候,可以在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

相关文章
|
6月前
|
安全 前端开发 Java
JDK源码级别彻底剖析JVM类加载机制
JDK源码级别彻底剖析JVM类加载机制
|
24天前
|
设计模式 网络协议 Java
06.动态代理设计模式
本文详细介绍了动态代理设计模式,包括其必要性、概念、实现方式及案例分析。动态代理允许在运行时动态创建代理对象,增强代码复用性和灵活性,减少类膨胀。文章通过对比静态代理,深入解析了动态代理的实现机制,如基于接口和类的动态代理,以及其在Retrofit中的应用。同时,讨论了动态代理的优势和潜在问题,如性能开销和调试难度。最后,提供了丰富的学习资源链接,帮助读者进一步理解和掌握动态代理。
17 1
|
1月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
20 1
|
2月前
|
设计模式 存储 缓存
JDK中都用了哪些设计模式?
JDK中都用了哪些设计模式?
49 7
|
3月前
|
算法 安全 Java
深入JDK源码:揭开ConcurrentHashMap底层结构的神秘面纱
【8月更文挑战第24天】`ConcurrentHashMap`是Java并发编程中不可或缺的线程安全哈希表实现。它通过精巧的锁机制和无锁算法显著提升了并发性能。本文首先介绍了早期版本中使用的“段”结构,每个段是一个带有独立锁的小型哈希表,能够减少线程间竞争并支持动态扩容以应对高并发场景。随后探讨了JDK 8的重大改进:取消段的概念,采用更细粒度的锁控制,并引入`Node`等内部类以及CAS操作,有效解决了哈希冲突并实现了高性能的并发访问。这些设计使得`ConcurrentHashMap`成为构建高效多线程应用的强大工具。
54 2
|
3月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
218 0
|
3月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
180 0
|
3月前
|
设计模式 算法 安全
jdk8中都使用了哪些设计模式?
【8月更文挑战第18天】jdk8中都使用了哪些设计模式?
30 0
|
3月前
|
设计模式 算法 Java
面试官:JDK中都用了哪些设计模式?
面试官:JDK中都用了哪些设计模式?
42 0
|
3月前
|
设计模式 Java API
设计模式-------------静态/动态代理模式(结构型设计模式)
本文详细介绍了代理模式,包括其定义、应用场景、UML类图、代码实现和实际例子,阐述了静态代理和动态代理的区别以及它们的优缺点,展示了如何通过代理模式来控制对目标对象的访问并增强其功能。