Spring5源码(27)-静态代理模式和JDK、CGLIB动态代理

简介: Spring5源码(27)-静态代理模式和JDK、CGLIB动态代理


前面的章节,已经分析了IoC容器的源码,接下来的章节来分析Spring的另一个核心功能AOP。为了更好的分析源码,需要先温习一下动态代理的知识,如果对java的动态代理无所了解的话,那么对AOP源码的分析就无从谈起。代理模式可分为静态代理和动态代理两种。而动态代理又有JDK、CGLIB动态代理。下面我们逐步分析这几种代理。

1.静态代理

  • 被代理接口和实现类

package com.lyc.cn.v2.day04.proxy.proxy;
/**
 * 账户接口
 */
public interface Count {
    // 查询账户
    void queryCount();
    // 修改账户
    void updateCount();
}

package com.lyc.cn.v2.day04.proxy.proxy;
/**
 * @author: LiYanChao
 * @create: 2018-10-21 12:07
 */
public class CountImpl implements Count {
    @Override
    public void queryCount() {
        System.out.println("==查询账户");
    }
    @Override
    public void updateCount() {
        System.out.println("==更新账户");
    }
}

接口只模拟了两个接口,账户查询和账户更新,并在实现类里进行了打印。

  • 代理类

package com.lyc.cn.v2.day04.proxy.proxy;
/**
 * 代理类
 * @author: LiYanChao
 * @create: 2018-10-21 12:08
 */
public class CountProxy implements Count {
    private CountImpl countImpl;
    /**
     * 覆盖默认构造器
     */
    public CountProxy(CountImpl countImpl) {
        this.countImpl = countImpl;
    }
    @Override
    public void queryCount() {
        System.out.println("==查询账户开始");
        // 调用真正的查询账户方法
        countImpl.queryCount();
        System.out.println("==查询账户结束");
    }
    @Override
    public void updateCount() {
        System.out.println("==更新账户开始");
        // 调用真正的修改账户操作
        countImpl.updateCount();
        System.out.println("==更新账户结束");
    }
}
  • 测试及结果

@Test
public void test1() {
    // 静态代理
    CountImpl countImpl = new CountImpl();
    CountProxy countProxy = new CountProxy(countImpl);
    countProxy.updateCount();
    System.out.println("\n*******\n");
    countProxy.queryCount();
}

==更新账户开始
==更新账户
==更新账户结束
*******
==查询账户开始
==查询账户
==查询账户结束

该中模式比较简单,不再做过多的分析。

2.JDK动态代理

JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的业务实现类对象以及方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。JDK的动态代理需要实现InvocationHandler接口,并重写invoke方法。并且被代理的类必须有接口,来看具体的例子:

  • 被代理接口和类

package com.lyc.cn.v2.day04.proxy.jdk;
public interface JDKAnimal {
    void sayHello();
}

package com.lyc.cn.v2.day04.proxy.jdk;
public class JDKDog implements JDKAnimal {
    @Override
    public void sayHello() {
        System.out.println("我是一只猫");
    }
}
  • 自定义InvocationHandler

package com.lyc.cn.v2.day04.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandler implements InvocationHandler {
    // 目标对象
    private Object target;
    /**
     * 构造方法
     * @param target 目标对象
     */
    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }
    /**
     * @param proxy  JDK动态生成的最终代理对象
     * @param method 调用真实对象的某个方法的Method对象
     * @param args   调用真实对象某个方法时接受的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("==代理方法开始执行");
        Object invoke = method.invoke(target, args);
        System.out.println("==代理方法结束执行");
        return invoke;
    }
    /**
     * 获取目标对象的代理对象
     * @return 代理对象
     */
    public Object getProxy() {
        /**
         * 参数:
         * 1、获取当前线程的类加载
         * 2、获取接口
         * 3、当前对象
         */
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
}
  • 测试及结果

@Test
public void test2() {
    // JDK动态代理
    MyInvocationHandler handler = new MyInvocationHandler(new JDKDog());
    JDKAnimal proxy = (JDKAnimal) handler.getProxy();
    proxy.sayHello();
}

==代理方法开始执行
我是一只猫
==代理方法结束执行
3.JDK动态代理原理分析

在MyInvocationHandler类中,通过实现InvocationHandler并重写invoke方法,实现了对JDKDog类的动态代理。但是这里大家一定会有一个疑问,在测试类中通过JDKAnimal proxy = (JDKAnimal) handler.getProxy();获取了代理类的实例,但是当调用proxy.sayHello();方法时,却会调用MyInvocationHandler的invoke方法,要解开这个谜题,就需要了解一下代理类是如何生成、生成的代理类是什么样子的、是如何被实例化的。

  • Proxy.newProxyInstance方法简析
    打开MyInvocationHandler类的getProxy方法,查看Proxy.newProxyInstance方法源码:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
    Objects.requireNonNull(h);
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, intfs);
    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        // 创建生成代理类的实例
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

如果大家对这段代码不感兴趣或者觉得很枯燥的话,没关系,大家只要知道在这个方法里生成了代理类的实例就行了,但是生成的代理类是什么样子的呢?可以通过ProxyGenerator来帮助我们。

  • 生成代理类的.class文件

package com.lyc.cn.v2.day04.proxy.jdk;
import org.junit.Test;
import sun.misc.ProxyGenerator;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
 * @author: LiYanChao
 * @create: 2018-11-02 14:11
 */
public class GenJdkProxyClass {
    /**
     * 生成代理类的名称
     */
    private static String DEFAULT_CLASS_NAME = "$Proxy";
    /**
     * 默认生成的文件全路径
     */
    private static String DEFAULT_FILE_PATH = "/Users/liyanchao/Desktop/" + DEFAULT_CLASS_NAME + ".class";
    /**
     * 使用ProxyGenerator生成代理类.class文件
     * @param path 文件路径
     */
    public static void genProxyClass(String path) {
        byte[] classFile = ProxyGenerator.generateProxyClass(DEFAULT_CLASS_NAME, new Class[]{JDKAnimal.class});
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(path);
            fos.write(classFile);
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    @Test
    public void TestGenProxyClass() {
        GenJdkProxyClass.genProxyClass(DEFAULT_FILE_PATH);
    }
}

可以根据自己的需要修改生成的类名和路径。运行测试方法生成.class文并进行反编译,如果没有反编译软件的话,可以下载JD-GUI 可以实现对.class文件的反编译,该软件支持windows、linux、macOS。用JD-GUI打开生成的$Proxy.class文件,并将反编译的类导入IDEA(直接在IDEA里新建同名文件将代码复制粘贴进去即可)。反编译后的源码如下:

package com.lyc.cn.v2.day04.proxy.jdk;
/**
 * 反编译代理.class文件
 * @author: LiYanChao
 * @create: 2018-11-02 15:01
 */
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy extends Proxy implements JDKAnimal {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
    public $Proxy(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
    }
    public final boolean equals(Object paramObject) {
        try {
            return ((Boolean) this.h.invoke(this, m1, new Object[]{paramObject})).booleanValue();
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
    /**
     * 代理类中实现了sayHello接口
     */
    public final void sayHello() {
        try {
            this.h.invoke(this, m3, null);
            return;
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
    public final String toString() {
        try {
            return (String) this.h.invoke(this, m2, null);
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
    public final int hashCode() {
        try {
            return ((Integer) this.h.invoke(this, m0, null)).intValue();
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m3 = Class.forName("com.lyc.cn.v2.day04.proxy.jdk.JDKAnimal").getMethod("sayHello", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            // return 会报错,注释掉即可。
            //return;
        } catch (NoSuchMethodException localNoSuchMethodException) {
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        } catch (ClassNotFoundException localClassNotFoundException) {
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
}

代理类$Proxy被声明为final类,继承Proxy类并实现了JDKAnimal接口,重写了equals、toString、hashCode等方法,当然最重要的还是实现了JDKAnimal接口中的sayHello方法,并通过静态代码块拿到了sayHello方法的信息,看到sayHello方法体,看到这里相信大家对前面提到的疑问已经恍然大悟了。再通过一个测试方法加深大家的印象:

@Test
public void test3() {
    // JDK动态代理测试反编译后的生成代理类
    MyInvocationHandler handler = new MyInvocationHandler(new JDKDog());
    $Proxy $proxy = new $Proxy(handler);
    $proxy.sayHello();
}

new $Proxy(handler)构造函数接受的就是我们自定义的MyInvocationHandler,所以当代码运行到$Proxy的sayHello方法时,this.h.invoke(this, m3, null); this.h就是MyInvocationHandler的实例,所以自然就会调用到invoke方法了,因为JDKAnimal proxy = (JDKAnimal) handler.getProxy();获取到的是代理类的实例,而不是JDKAnimal的实例。

4.CGLIB动态代理

CGLIB是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。 在使用的时候需要引入cglib和asm的jar包,并实现MethodInterceptor接口。

本例是在Spring源码下新建的Gradle模块,所以引入jar包的方式如下,如果是maven工程或者其他的工程的话,可自行导入jar包。

compile group: 'asm', name: 'asm', version: '3.3.1'
compile group: 'cglib', name: 'cglib', version: '2.2.2'
  • 被代理类

package com.lyc.cn.v2.day04.proxy.cglib;
public class CglibCat {
    public void sayHello() {
        System.out.println("我是一只猫。。。");
    }
}
  • 自定义MethodInterceptor

package com.lyc.cn.v2.day04.proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyCglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    // 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
    public Object getInstance(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        // 返回代理对象
        return enhancer.create();
    }
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("==代理方法开始执行");
        Object result = methodProxy.invokeSuper(proxy, args);
        System.out.println("==代理方法结束执行");
        return result;
    }
}
  • 测试类及结果

@Test
public void test4() {
    // CGLIB动态代理
    CglibCat cat = (CglibCat) new MyCglibProxy().getInstance(CglibCat.class);
    cat.sayHello();
}

==代理方法开始执行
我是一只猫。。。
==代理方法结束执行

关于CGLIB的原理,经过多种反编译软件测试,反编译出来的代码好像不是正确的,而且跟网上其他的文章反编译出来的有很大的不同,不知道是不是因为macOS或CGLIB版本的原因,这里就不在粘贴反编译源码了,如果感兴趣的同学可以参考下面这篇文章,cglib原理解析

5.总结

总而言之,动态代理技术是AOP的基础也是核心,大家一定要先把JDK、CGLIB动态代理搞明白之后,再去看AOP的源码,才能达到事半功倍的效果。

6.JDK与CGlib对比
  • CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高(1.6和1.7CGLib更快,1.8jdk更快
  • CGLib在创建对象的时候所花费的时间却比JDK动态代理多
  • singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反之,则适合用JDK动态代理
  • JDK动态代理是面向接口的,CGLib动态代理是通过字节码底层继承代理类来实现(如果被代理类被final关键字所修饰,那么会失败)
  • JDK生成的代理类类型是Proxy(因为继承的是Proxy),CGLIB生成的代理类类型是Enhancer类型
  • 如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);如果要被代理的对象不是实现类,那么Spring会强制使用CGLib来实现动态代理。&nbsp;



目录
相关文章
|
24天前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
35 4
|
29天前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
44 4
|
2月前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
32 0
[Java]静态代理与动态代理(基于JDK1.8)
|
2月前
|
Java 数据安全/隐私保护 Spring
Spring进阶:初识动态代理
本文介绍了Spring框架中AOP切面编程的基础——动态代理。通过定义Vehicle接口及其实现类Car和Ship,展示了如何使用动态代理在不修改原代码的基础上增强功能。文章详细解释了动态代理的工作原理,包括通过`Proxy.newProxyInstance()`方法创建代理对象,以及`InvocationHandler`接口中的`invoke()`方法如何处理代理对象的方法调用。最后,通过一个测试类`TestVehicle`演示了动态代理的具体应用。
|
3月前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
2月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
23 1
|
4月前
|
算法 安全 Java
深入JDK源码:揭开ConcurrentHashMap底层结构的神秘面纱
【8月更文挑战第24天】`ConcurrentHashMap`是Java并发编程中不可或缺的线程安全哈希表实现。它通过精巧的锁机制和无锁算法显著提升了并发性能。本文首先介绍了早期版本中使用的“段”结构,每个段是一个带有独立锁的小型哈希表,能够减少线程间竞争并支持动态扩容以应对高并发场景。随后探讨了JDK 8的重大改进:取消段的概念,采用更细粒度的锁控制,并引入`Node`等内部类以及CAS操作,有效解决了哈希冲突并实现了高性能的并发访问。这些设计使得`ConcurrentHashMap`成为构建高效多线程应用的强大工具。
58 2
|
4月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
284 0
|
4月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
202 0
|
Java Spring
Spring02——实现动态代理拦截器
实现动态代理的2种方式 第一种:  用JDK提供的Proxy代理类 和 InvocationHandler调用处理类  配合起来可以拦截一个接口下面的实现类,拦截它的方法,这样可以在它之前做点事情之后做点事情都可以。