SpringAOP-JDK 动态代理和 CGLIB 代理

简介: 在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类。 1.JDK 动态代理 那么接口(UserServiceBo)、目标对象(被代理对象 UserServiceImpl)、代理对象($Proxy0)三者具体关系可以使用下图表示: 正如上图可知 JDK 动态代理是对接口进行的代理;代理类实现了接口,并继承了 Proxy 类;目标对象与代理对象没有什么直接关系,只是它们都实现了接口,并且代理对象执行方法时候内部最终是委托目标对象执行具体的方法。

在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类。

1.JDK 动态代理

那么接口(UserServiceBo)、目标对象(被代理对象 UserServiceImpl)、代理对象($Proxy0)三者具体关系可以使用下图表示:

正如上图可知 JDK 动态代理是对接口进行的代理;代理类实现了接口,并继承了 Proxy 类;目标对象与代理对象没有什么直接关系,只是它们都实现了接口,并且代理对象执行方法时候内部最终是委托目标对象执行具体的方法。

 

示例如下:

JDK 代理是对接口进行代理,所以首先写一个接口类:

public interface UserService {

    public  int add();
}

 

然后实现该接口如下:

public class UserServiceImpl implements UserService {

    @Override
    public int add() {
        System.out.println("执行add方法");
        return 0;
    }
}

JDK 动态代理是需要实现 InvocationHandler 接口,因此创建一个 InvocationHandler 的实现类:

public class MyInvocationHandler implements InvocationHandler {

    private Object target;

    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1
        System.out.println("********begin "+method.getName()+"********");
        //2
        Object result = method.invoke(target, args);
        //3
        System.out.println("********end "+method.getName()+"*********");
        return result;
    }

    public Object getProxy(){
        //4
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
    }

}

 

接着进行测试,代码如下:

public static void main(String[] args) {

  //5打开这个开关,可以把生成的代理类保存到磁盘
 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");  
  //6创建目标对象(被代理对象)
  UserService service = new UserServiceImpl();
  //7创建一个InvocationHandler实例,并传递被代理对象
  MyInvocationHandler handler = new MyInvocationHandler(service);
  //8生成代理类
  UserService proxy = (UserService) handler.getProxy();
  proxy.add();
}

其中代码6 创建了一个 UserServiceImpl 的实例对象,这个对象就是要被代理的目标对象。

代码7 创建了一个 InvocationHandler 实例,并传递被代理目标对象 service 给内部变量 target。

代码8 调用 MyInvocationHandler 的 getProxy 方法使用 JDK 生成一个代理对象。

代码5 设置系统属性变量 sun.misc.ProxyGenerator.saveGeneratedFiles 为 true,这是为了让代码(8)生成的代理对象的字节码文件保存到磁盘。

 

接着我们进行反编译看核心的代码,运行后会在项目的 com.sun.proxy 下面会生成 $Proxy0.class 类,经反编译后核心 Java 代码如下:

package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.JDK.UserServiceBo;

public final class $Proxy0
  extends Proxy
  implements UserService
{
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m2;

  public $Proxy0(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }

  public final int add()
  {
    try
    {
    //9第一个参数是代理类本身,第二个是实现类的方法,第三个是参数
      return h.invoke(this, m3, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

 ...省略

  static
  {
    try
    {

      m3 = Class.forName("proxy.JDK.UserService").getMethod("add", new Class[0]);
    ...省略
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    ...省略
  }
}

可以看到代理类 $Proxy0 中 代码9  中的 h 就是 main 函数里面创建的 MyInvocationHandler 的实例,h.invoke(this, m3, null) 实际就是调用的 MyInvocationHandler 的 invoke 方法,

而后者则是委托给被代理对象进行执行,这里可以对目标对象方法进行拦截,然后对其功能进行增强。另外 代码8 生成的 proxy 对象实际就是 $Proxy0 的一个实例,当调用 proxy.add() 时候,

实际是调用的代理类 $Proxy0 的 add 方法,后者内部则委托给 MyInvocationHandler 的 invoke 方法,invoke 方法内部有调用了目标对象 service 的 add 方法。

 

因此,JDK 动态代理机制总结如下:

JDK 动态代理机制只能对接口进行代理,其原理是动态生成一个代理类,这个代理类实现了目标对象的接口,目标对象和代理类都实现了接口,但是目标对象和代理类的 Class 对象是不一样的,所以两者是没法相互赋值的。

 

 

CGLIB 动态代理

相比 JDK 动态代理对接口进行代理,CGLIB 则是对实现类进行代理,这意味着无论目标对象是否有接口,都可以使用 CGLIB 进行代理。

那么接口(UserServiceBo)、目标对象(被代理对象 UserServiceImpl),代理对象(UserServiceImpl$$EnhancerByCGLIB$$d0bce05a)三者具体关系可以使用下图表示:

可知接口和代理对象没有啥关系,代理对象是继承了目标对象和实现了 Factory 接口。

例子如下:

使用 CGLIB 进行代理需要实现 MethodInterceptor,创建一个方法拦截器 CglibProxy 类:

 

public class CglibProxy implements MethodInterceptor {
    //10
    private Enhancer enhancer = new Enhancer();
    //11
    public Object getProxy(Class clazz) {
        //12 设置被代理类的Class对象
        enhancer.setSuperclass(clazz);
        //13 设置拦截器回调
        enhancer.setCallback( this);
        return enhancer.create();
    }


    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(obj.getClass().getName()+"."+method.getName());
       Object result = proxy.invokeSuper(obj, args);

        return result;
    }
}

测试类如下:

public void testCglibProxy() {

//14 生成代理类到本地
 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhuizhumengxiang/Downloads");
    //15 生成目标对象
    UserServiceImpl service = new UserServiceImpl();
    //16 创建CglibProxy对象
    CglibProxy cp = new CglibProxy();
    //17 生成代理类
    UserServiceBo proxy = (UserServiceBo) cp.getProxy(service.getClass());
    proxy.add();
}

 

执行上面代码会在 /Users/huangjuncong/Downloads 目录生成代理类 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a.class 文件,反编译后部分代码如下:

 

public class UserServiceImpl$$EnhancerByCGLIB$$d0bce05a extends UserServiceImpl
  implements Factory
{
static void CGLIB$STATICHOOK1()
  {
    //18 空参数
    CGLIB$emptyArgs = new Object[0];

    //19 获取UserServiceImpl的add方法列表
    Method[] tmp191_188 = ReflectUtils.findMethods(new String[] { "add", "()I" }, (localClass2 = Class.forName("zlx.cglib.zlx.UserServiceImpl")).getDeclaredMethods());
    CGLIB$add$0$Method = tmp191_188[0];

    //20  创建CGLIB$add$0,根据创建一个MethodProxy
    CGLIB$add$0$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "add", "CGLIB$add$0");

  }
  static
  {
    CGLIB$STATICHOOK1();
  }
  //(21)
  final int CGLIB$add$0()
  {
    return super.add();
  }
  //(22)
  public final int add()
  {
    ...省略
    //23 获取拦截器,这里为CglibProxy的实例
    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
    if (tmp17_14 != null)
    { 
    //24 调用拦截器的intercept方法 Object tmp36_31 = tmp17_14.intercept(this, CGLIB$add$0$Method, CGLIB$emptyArgs, CGLIB$add$0$Proxy); tmp36_31; return tmp36_31 == null ? 0 : ((Number)tmp36_31).intValue(); } return super.add(); } }

代码18 创建了一个 CGLIB$emptyArgs,因为 add 方法是无入参的,所以这里创建的 Object 对象个数为0,这对象是 CglibProxy 拦截器的 intercept 的第三个参数。

代码19 获取 UserServiceImpl 的 add 方法列表,并把唯一方法赋值给 CGLIB$add$0$Method,这个对象是 CglibProxy 拦截器的 intercept 第二个参数。

代码20 创建了一个 MethodProxy 对象,当调用 MethodProxy 对象的 invokeSuper 方法时候会直接调用代理对象的 CGLIB$add$0 方法,也就是直接调用父类 UserServiceImpl 的 add 方法,这避免了一次反射调用,创建的 MethodProxy 对象是 CglibProxy 拦截器的 intercept 的第四个参数。

代码22 是代理类的 add 方法,代码(17)生成的代理对象 proxy 就是 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a 的一个实例,当调用 proxy.add() 方法时候就是调用的代码(22),从代码(22)可知内部调用了拦截器 CglibProxy 的 intercept 方法,可知 intercept 的第一个参数就是代理对象本身。

 

  因此CGLIB代理的总结如下:

    CGLIB 是对目标对象本身进行代理,所以无论目标对象是否有接口,都可以对目标对象进行代理,其原理是使用字节码生成工具在内存生成一个继承目标对象的代理类,然后创建代理对象实例。

    由于代理类的父类是目标对象,所以代理类是可以赋值给目标对象的,自然如果目标对象有接口,代理对象也是可以赋值给接口的。

    CGLIB 动态代理中生成的代理类的字节码相比 JDK 来说更加复杂。

 

总的来说

  JDK 使用反射机制调用目标类的方法,CGLIB 则使用类似索引的方式直接调用目标类方法,所以 JDK 动态代理生成代理类的速度相比 CGLIB 要快一些,但是运行速度比 CGLIB 低一些,并且 JDK 代理只能对有接口的目标对象进行代理。

目录
相关文章
|
21天前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
38 4
|
2月前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
30 0
[Java]静态代理与动态代理(基于JDK1.8)
|
2月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
22 1
|
4月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
268 0
|
4月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
198 0
|
3月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
361 3
|
4月前
|
Oracle Java 关系型数据库
Mac安装JDK1.8
Mac安装JDK1.8
759 4
|
4月前
|
Java 关系型数据库 MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【8月更文挑战第19天】在Linux上搭建Java Web应用环境,需安装JDK 1.8、Tomcat及MariaDB。本指南详述了使用apt-get安装OpenJDK 1.8的方法,并验证其版本。接着下载与解压Tomcat至`/usr/local/`目录,并启动服务。最后,通过apt-get安装MariaDB,设置基本安全配置。完成这些步骤后,即可验证各组件的状态,为部署Java Web应用打下基础。
62 1
|
22天前
|
Oracle Java 关系型数据库
安装 JDK 时应该注意哪些问题
选择合适的JDK版本需考虑项目需求与兼容性,推荐使用LTS版本如JDK 17或21。安装时注意操作系统适配,配置环境变量PATH和JAVA_HOME,确保合法使用许可证,并进行安装后测试以验证JDK功能正常。
44 1
|
22天前
|
IDE Java 编译器
开发 Java 程序一定要安装 JDK 吗
开发Java程序通常需要安装JDK(Java Development Kit),因为它包含了编译、运行和调试Java程序所需的各种工具和环境。不过,某些集成开发环境(IDE)可能内置了JDK,或可使用在线Java编辑器,无需单独安装。
48 1