当前位置: 首页 > news >正文

【Java知识】java进阶-手撕动态代理

文章目录

    • 概述
      • 动态代理的两种实现方式
      • 基于接口的动态代理(JDK动态代理)
        • 实现原理
        • 示例代码
      • 基于类的动态代理(CGLIB动态代理)
        • 实现原理
        • 示例代码
      • 动态代理的实际应用
    • 两种方式的比较
      • JDK动态代理
      • CGLIB动态代理
      • 性能对比
      • 总结

概述

Java动态代理是一种设计模式,允许在运行时动态创建代理对象,并在不修改原始类的情况下增强其功能。它主要用于拦截方法调用,进行日志记录、性能监控、事务管理等操作。

动态代理的两种实现方式

  1. 基于接口的动态代理(JDK动态代理)
  2. 基于类的动态代理(CGLIB动态代理)

基于接口的动态代理(JDK动态代理)

JDK动态代理主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。它只能代理实现了接口的类。

实现原理

Java动态代理的底层原理主要依赖于Java的反射机制(Reflection)。以下是JDK动态代理的底层实现原理的详细解释:

  1. java.lang.reflect.Proxy:这是所有动态生成的代理类的父亲类。它提供了一个静态方法newProxyInstance,用于在运行时创建一个新的代理实例。

  2. java.lang.reflect.InvocationHandler接口:所有代理实例必须关联一个InvocationHandler实例。当代理实例上的方法被调用时,调用会自动委托给这个InvocationHandler实例的invoke方法。

  3. 动态字节码生成:当Proxy.newProxyInstance方法被调用时,JVM会使用ProxyClassFactory生成一个动态的代理类。这个代理类是被代理接口的一个实现,但它不是通过传统方式实现的,而是在运行时动态生成的。

  4. 代理类的加载:生成的代理类的字节码会被加载到JVM中,这通常通过调用类加载器的defineClass方法完成。

  5. 代理实例的创建:一旦代理类被加载,JVM会使用它的构造方法创建一个新的实例。这个构造方法接受一个InvocationHandler作为参数,该参数在代理实例的方法调用时会被使用。

  6. 方法调用的分派:当代理实例上的任何方法被调用时,JVM会将调用转发到关联的InvocationHandler实例的invoke方法。invoke方法的三个参数是:代理实例本身、被调用的方法的Method对象和方法参数。

  7. invoke方法的逻辑:在invoke方法内部,你可以定义任何自定义逻辑。通常,你会在这个方法中调用原始对象的方法,并在调用前后添加额外的处理逻辑。

  8. 返回结果invoke方法的返回值会成为代理方法的返回值。

  9. 缓存机制:为了性能优化,JVM会缓存生成的代理类。如果同一个组合(接口列表和调用处理器)再次请求一个代理实例,JVM会从缓存中获取代理类,而不是重新生成。

这个过程涉及到的核心技术是Java的反射机制,它允许程序在运行时查询和操作类和对象的属性和方法。动态代理利用了这一点,通过在运行时动态创建类和对象,实现了方法调用的动态分派。

CGLIB动态代理的实现原理与JDK动态代理不同。CGLIB通过使用ASM字节码操作框架在运行时动态生成被代理类的子类来实现代理。这个子类覆盖了父类的方法,并在这些方法中添加了额外的逻辑。当方法被调用时,实际上是调用了子类的覆盖方法,从而实现了方法调用的拦截和增强。CGLIB可以代理没有实现接口的类,但它需要目标类不是final的,因为final类不能被继承。

示例代码

首先,我们定义一个接口和其实现类:

public interface Service {void perform();
}public class ServiceImpl implements Service {@Overridepublic void perform() {System.out.println("Service is performing...");}
}

接下来,我们定义一个动态代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class ServiceProxy implements InvocationHandler {private final Object target;public ServiceProxy(Object target) {this.target = target;}public Object getProxy() {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method invoke");Object result = method.invoke(target, args);System.out.println("After method invoke");return result;}
}

最后,使用动态代理:

public class ProxyTest {public static void main(String[] args) {Service service = new ServiceImpl();ServiceProxy serviceProxy = new ServiceProxy(service);Service proxyInstance = (Service) serviceProxy.getProxy();proxyInstance.perform();}
}

运行结果:

Before method invoke
Service is performing...
After method invoke

基于类的动态代理(CGLIB动态代理)

CGLIB(Code Generation Library)是一个强大的字节码生成库,它允许创建基于类的代理,而无需实现接口。CGLIB通过继承目标类并重写其方法来实现代理。

实现原理

CGLIB(Code Generation Library)是一个功能强大的高性能代码生成库,它通过使用字节码处理框架ASM在运行时动态生成类的子类。CGLIB动态代理的实现原理主要包括以下几个方面:

  1. 字节码生成:CGLIB使用ASM框架来操作字节码,动态生成被代理类的子类。这是CGLIB动态代理的核心,它允许在运行时扩展类的功能而无需修改原类代码。

  2. 方法拦截:CGLIB通过创建被代理类的子类,并重写其中的方法来实现代理。在子类中,可以拦截对父类方法的调用,并在方法执行前后添加自定义逻辑,如日志记录、权限检查等。

  3. FastClass机制:CGLIB提供了FastClass机制,它通过创建索引来快速定位方法,避免了反射调用方法时的性能开销。这种方法调用方式比传统的反射调用要快得多。

  4. 创建代理对象:使用CGLIB的Enhancer类可以创建代理对象。首先设置需要代理的目标类,然后设置一个MethodInterceptor作为回调,最后调用create方法生成代理实例。

  5. 代理类的生成:在调用Enhancer.create()方法时,CGLIB会动态生成一个代理类,这个类是被代理类的子类,并且重写了被代理类的所有非final方法。在代理类的方法中,会调用MethodInterceptorintercept方法,以便在原方法执行前后添加额外的行为。

  6. 性能考虑:虽然CGLIB在创建代理类时可能会有一些性能开销,但一旦代理类被创建,其方法调用的性能通常优于基于反射的JDK动态代理,因为它避免了反射调用的额外开销。

  7. 局限性:CGLIB不能代理final类或final方法,因为final类或方法无法被继承和重写。此外,生成代理类的过程可能比JDK动态代理慢,尤其是在第一次创建代理类时。

CGLIB动态代理的使用步骤通常包括:

  • 引入CGLIB依赖。
  • 定义一个目标类。
  • 创建一个实现了MethodInterceptor接口的拦截器类。
  • 使用Enhancer类创建代理对象。
  • 通过代理对象调用方法,方法调用会被拦截器拦截并处理。

CGLIB动态代理广泛应用于AOP框架中,如Spring AOP,以及需要在运行时动态生成代理对象的场景。它提供了一种灵活且高效的方式来增强类的功能。

示例代码

首先,引入CGLIB库:

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

然后,定义一个类,不实现任何接口:

public class CglibService {public void perform() {System.out.println("CglibService is performing...");}
}

接下来,定义一个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 CglibServiceProxy implements MethodInterceptor {private final Object target;public CglibServiceProxy(Object target) {this.target = target;}public Object getProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("Before method invoke");Object result = proxy.invokeSuper(obj, args);System.out.println("After method invoke");return result;}
}

最后,使用CGLIB动态代理:

public class CglibProxyTest {public static void main(String[] args) {CglibService service = new CglibService();CglibServiceProxy proxy = new CglibServiceProxy(service);CglibService proxyInstance = (CglibService) proxy.getProxy();proxyInstance.perform();}
}

运行结果:

Before method invoke
CglibService is performing...
After method invoke

动态代理的实际应用

  1. AOP(面向切面编程):动态代理是AOP的核心技术之一。AOP允许在不修改业务逻辑的情况下添加额外的功能,如日志记录、事务管理和权限控制。
  2. 远程方法调用(RMI):动态代理可用于创建远程服务的本地代理,使得调用远程方法与调用本地方法一样简便。
  3. Mock测试:在单元测试中,可以使用动态代理创建Mock对象,以模拟依赖对象的行为。

通过使用动态代理,Java开发者可以在运行时动态地创建代理对象,从而在不修改原始类代码的情况下增强其功能。

两种方式的比较

Java动态代理主要有两种实现方式:JDK动态代理和CGLIB动态代理。它们各有特点和适用场景,也存在一些局限性。

JDK动态代理

实现原理:JDK动态代理基于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。它在运行时动态生成一个实现了指定接口的代理类,并将方法调用转发到InvocationHandlerinvoke方法中处理。

优点

  • 实现简单,使用Java内置API。
  • 无需依赖第三方库。
  • 动态性,可以在运行时根据需要动态地创建代理对象。
  • 解耦,将目标对象与横切逻辑分离,降低了代码之间的耦合度。

局限性

  • 只能代理实现了接口的类,不能代理没有实现接口的类。
  • 性能开销,使用反射机制来调用目标方法,可能导致一定程度的性能损失。
  • 代理类数量,对于每个需要代理的接口,都会生成一个新的代理类,可能导致代理类数量过多。

CGLIB动态代理

实现原理:CGLIB(Code Generation Library)通过字节码生成技术,创建目标类的子类来实现代理。它可以代理没有实现接口的普通类,通过覆盖父类的方法来添加额外的行为。

优点

  • 可以代理没有接口的类。
  • 方法调用性能较高,避免了反射调用。

局限性

  • 创建代理类时需要进行字节码操作,性能开销较大。
  • 需要依赖cglib和ASM库。
  • 被代理类不能被final关键字修饰,否则无法生成子类。

性能对比

在性能方面,CGLIB动态代理通常比JDK动态代理更快,因为它不需要通过反射来调用方法。然而,CGLIB在生成和加载代理类的过程中可能会稍微慢一些,因为它涉及到字节码的生成和类加载。

总结

选择JDK动态代理还是CGLIB动态代理,取决于具体的需求:

  • 如果目标对象已经实现了接口,且代理逻辑相对简单,JDK动态代理是一个简单直接的选择。
  • 如果需要代理没有实现接口的类,或者对性能有较高要求,CGLIB动态代理是更合适的选择。

在实际开发中,Spring框架能够根据情况自动选择使用JDK动态代理或CGLIB来实现代理,提供了更高层次的抽象和便利。


http://www.mrgr.cn/news/48780.html

相关文章:

  • 【AI论文精读12】RAG论文综述2(微软亚研院 2409)P4-隐性事实查询L2
  • 16路舵机控制芯片lu9685使用技巧
  • 数据结构-5.4.二叉树的性质
  • 认识C++的变量与整型
  • threejs-补间动画Tween应用
  • [Linux] Linux 进程程序替换
  • 【C++】关联式容器——map和set的使用
  • 可观察性的三大支柱:统一日志、指标和跟踪
  • 衡石分析平台系统管理手册-智能运维之系统日志
  • SpringBoot接口异常:Request header is too large
  • MySQL表的操作
  • Git Commit 规范
  • 对偶范数(Dual Norm)
  • Java-学生管理系统[初阶]
  • uniapp-小程序开发0-1笔记大全
  • sklearn pipeline
  • 中科星图GVE(案例)——AI实现建筑用地变化前后对比情况
  • node.js服务器基础
  • C++笔记---红黑树的插入删除
  • 数据结构-5.2.树的性质