精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(中)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 经历了上一篇文章内容:《精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(上)》,相信您对于Java原生的动态代理技术应该有了一定的认识和了解了,那么我们先来回顾一下对应的技术要点,看看您是否真正的认识了对应的技术原理了?

前提介绍

经历了上一篇文章内容:《精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(上)》,相信您对于Java原生的动态代理技术应该有了一定的认识和了解了,那么我们先来回顾一下对应的技术要点,看看您是否真正的认识了对应的技术原理了?


技术回顾

要回顾Java动态代理,需要考虑以下几个关键点:
在这里插入图片描述

  • 动态代理的工作原理:动态代理通过在运行时动态创建代理对象来实现对目标对象的增强或修改。代理对象会拦截目标对象的所有方法调用,并在调用前后执行特定的逻辑。

  • 接口的重要性:动态代理要求目标对象和代理对象都实现一个或多个相同的接口。这些接口定义了代理对象和目标对象可以调用的方法。通过这种方式,客户端代码可以像调用普通接口一样调用代理对象,而无需关心其实现细节。

  • 实现动态代理的步骤:创建目标对象实现的所有接口的类,作为代理类的基础;在代理类中,使用Proxy.newProxyInstance()方法动态创建代理对象;在代理类中,重写目标对象实现的所有接口的方法,以便在调用这些方法时执行自定义逻辑。

  • 代理模式的应用场景:动态代理适用于需要对目标对象进行增强或修改的场景。例如,在不修改目标对象的源代码的情况下,为对象添加日志记录、性能监控、事务处理等功能。

注意:使用动态代理时,需要确保目标对象和代理对象都实现了相同的接口,否则客户端代码将无法正确调用代理对象。另外,动态代理不适用于非接口方法,因为Java不支持多重继承,代理对象只能继承自一个类

回顾问题分析

代理对象实现了什么接口

目标对象实现的接口即为所实现的接口,这与静态代理模式中代理对象实现的接口是相同的。此外,代理对象和目标对象都共有一个共同的接口,即它们都继承自同一个接口。因此,Proxy.newProxyInstance()方法返回的类型就是该接口类型。

代理对象的方法体是什么

代理对象的方法体是拦截器中invoke方法的实现,用于拦截并处理代理对象的所有逻辑,控制是否执行目标对象的目标方法。这个方法通常包括对请求的处理、对目标方法的调用以及对结果的返回等操作。

通过代理对象的方法体,可以实现方法级别的拦截和过滤,以及对目标方法的调用进行扩展和增强。同时,代理对象的方法体还可以用于实现事务管理、日志记录、安全控制等高级功能。


Java动态代理为我们提供了非常灵活的代理机制,但Java动态代理是基于接口的,如果对象没有实现接口我们该如何代理呢

CGLIB动态代理

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它具备强大的运行时字节码修改和动态生成能力。

CGLIB的原理

CGLIB需要指定父类和回调方法。当然cglib也可以与Java动态代理一样面向接口,因为本质是继承

CGLIB通过继承方式实现代理,能够动态地生成子类,覆盖并重写目标对象的方法,这种能力使得CGLIB成为Spring框架中实现AOP(面向切面编程)的关键组件。

继承方式

这种动态生成和修改字节码的能力使得CGLIB在许多高级应用场景中,如AOP(面向切面编程)编程、动态代理、方法拦截等,都展现出了卓越的性能和灵活性。

通过使用CGLIB,开发人员可以在运行时动态地定义横切关注点,例如日志记录、事务管理、安全控制等,而无需修改原有的业务逻辑代码。

为什么要用CGLIB

选择CGLIB来实现动态代理的主要原因之一是Spring框架的使用。Spring框架广泛应用于企业级应用程序的开发,而CGLIB作为基于ASM的字节码生成库,与Spring框架紧密集成,提供了强大的运行时字节码修改和动态生成能力,以下是使用CGLIB实现动态代理的简单示例代码:

建立被代理的类

首先,创建一个简单的代理对象,并不需要实现复杂的接口,那么可以直接创建一个实现类对象作为目标类,然后通过CGLIB或其他字节码库动态地修改这个类的字节码,生成代理类。

/** 
 * 被代理的类 
 * 目标对象类 
 */  
public class TargetObject {
   
     
    /** 
     * 目标方法(即目标操作) 
     */  
    public void exec() {
   
     
        System.out.println("exec");  
    }  
}
AI 代码解读

cglib拦截器类

接下来,我们将实现一个MethodInterceptor。通过该实现,所有的方法调用将被转发到intercept()方法进行处理。

在这里插入图片描述
这种方法拦截器提供了一种机制,可以在方法调用之前和之后执行自定义逻辑,从而实现更高级的功能,如日志记录、事务管理、安全控制等。

/** 
 * 动态代理-拦截器 
 */  
public class MyInterceptor implements MethodInterceptor {
   
     

    private Object target;//目标类  

    public MyInterceptor(Object target) {
   
     
        this.target = target;  
    }  

    /** 
     * 返回代理对象 
     * 具体实现,暂时先不追究。 
     */  
    public Object createProxy() {
   
     
        Enhancer enhancer = new Enhancer();  
        enhancer.setCallback(this);//回调方法 拦截器  
        //设置代理对象的父类,可以看到代理对象是目标对象的子类。所以这个接口类就可以省略了。  
        enhancer.setSuperclass(this.target.getClass());  
        return enhancer.create();  
    }  

}
AI 代码解读

上述代码中,我们通过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里我们可以加入任何逻辑。

  /** 
     * args 目标方法的参数 
     * method 目标方法 
     */  
    @Override  
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 
        throws Throwable {
   
     
        System.out.println("Before method invocation");  
        //调用目标类的目标方法  
        method.invokeSuper(this.target, objects);
        System.out.println("After method invocation");  
        return null;  
   }
AI 代码解读

通过调用 MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是TargetObject 的具体方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很类似,都是方法调用的中转站。

测试类

在需要使用TargetObject时,我们可以通过CGLIB的动态代理机制来获取一个代理对象。通过这种方式,我们可以在运行时动态地创建代理对象,而无需修改TargetObject的源代码。
在这里插入图片描述
这个代理对象将继承自TargetObject,并且所有的方法调用都会被拦截并转发到指定的处理逻辑中。这种机制提供了极大的灵活性和可扩展性,使得我们可以在不改变原有业务逻辑的情况下,实现各种附加功能。

public class MainTest {
   
     
    public static void main(String[] args) {
   
     
        //目标对象  
        TargetObject target = new TargetObject();  
        //拦截器  
        MyInterceptor myInterceptor = new MyInterceptor(target);  
        //代理对象,调用cglib系统方法自动生成  
        //注意:代理类是目标类的子类。  
        TargetObject proxyObj = (TargetObject) myInterceptor.createProxy();  
        proxyObj.exec();  
    }  
}
AI 代码解读

我们分析了一下,从文件数上来说,cglib比jdk实现的少了个接口类。因为cglib返回的代理对象是目标对象的子类。而jdk产生的代理对象和目标对象都实现了一个公共接口。

易错点:CGLIB的invoke和invokeSuper的区分

在CGLIB中,MethodProxy是一个非常重要的接口,它提供了对被代理方法的低级别访问。MethodProxy的invoke和invokeSuper方法都是用于调用目标方法的。

invoke方法

当你调用代理对象的某个方法时,这个方法会被转发到MethodProxy的invoke方法,invoke方法接受四个参数:被代理对象、被代理方法、被代理方法的参数以及一个MethodProxy对象。

在invoke方法中,你可以使用MethodProxy的getMethod方法来获取被代理方法的详细信息(如方法名、参数类型等),然后你可以决定是否转发该方法调用或者执行其他逻辑。

invokeSuper方法:

invokeSuper方法是MethodProxy接口中的一个重要方法,用于执行被代理方法的调用,这个方法的调用会触发对目标对象的实际方法调用,从而使得我们可以对目标方法进行拦截和增强。

使用invokeSuper,我们可以调用目标对象上的原始方法,并且可以在这个调用前后添加额外的逻辑。在CGLIB的动态代理中,通常在拦截器的intercept方法中使用MethodProxy.invokeSuper来调用目标方法,从而实现方法的拦截和增强。

注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不会,因为它是final方法,CGLIB无法代理

TargetObject代理对象的类型信息

如果对CGLIB代理之后的对象类型进行深挖,可以看到如下信息:

class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
superClass=class lh.HelloConcrete

 interfaces: 
 interface net.sf.cglib.proxy.Factory
 invocationHandler=not java proxy class
AI 代码解读
  • 看到使用CGLIB代理之后的对象类型是cglib.TargetObjectEnhancerByCGLIB

    e3734e52,这是CGLIB动态生成的类型;父类是TargetObject,印证了CGLIB是通过继承实现代理;

  • 同时实现了net.sf.cglib.proxy.Factory接口,这个接口是CGLIB自己加入的,包含一些工具方法。

final控制以及限制

既然是继承就不得不考虑final的问题。我们知道final类型不能有子类,所以CGLIB不能代理final类型,遇到这种情况会抛出类似如下异常:

java.lang.IllegalArgumentException: Cannot subclass final class cglib.TargetObject
AI 代码解读

同样的,final方法是不能重载的,所以也不能通过CGLIB代理,遇到这种情况不会抛异常,而是会跳过final方法只代理其他方法。如果你还对代理类cglib.TargetObjectEnhancerByCGLIB

e3734e52具体实现感兴趣,它大致长这个样子:

CGLIB代理类具体实现

public class TargetObject{
   
   mathJaxContainer[3]}e3734e52
  extends TargetObject
  implements Factory
{
   
   
  ...
  private MethodInterceptor CGLIB$CALLBACK_0; // ~~
  ...

  public final String exec()
  {
   
   
    ...
    MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
   
   
      // 将请求转发给MethodInterceptor.intercept()方法。
      return (String)tmp17_14.intercept(this, 
              CGLIB$exec$0$Method, 
              new Object[] {
   
    }, 
              CGLIB$exec$0$Proxy);
    }
    return super.exec();
  }
  ...
}
AI 代码解读

上述代码我们看到,当调用代理对象的exec()方法时,首先会尝试转发给MethodInterceptor.intercept()方法,如果没有MethodInterceptor就执行父类的exec()。这些逻辑没什么复杂之处,但是他们是在运行时动态产生的,无需我们手动编写。

未完待续

由于篇幅过程,小编,会在下一章深入分一下cglib的底层实现以及生产详细的class类的内容,以及底层的细节原理,请《精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(下)》


归纳总结

动态代理是一种技术,它可以在运行时动态地创建代理对象,拦截对目标对象的调用并执行一些额外的逻辑。动态代理主要分为两种实现方式:JDK的动态代理和CGLIB的动态代理。
在这里插入图片描述

JDK的动态代理

  • 代理对象和目标对象需要实现共同的接口。
  • 拦截器(或处理器)必须实现InvocationHandler接口。
  • JDK的动态代理主要适用于目标对象实现了某个接口的情况。

    CGLIB的动态代理

  • 代理对象是目标对象的子类。

  • 拦截器必须实现MethodInterceptor接口。
  • CGLIB不仅支持基于接口的代理,还支持基于类的代理。这意味着即使目标对象没有实现接口,仍然可以使用CGLIB创建代理对象。

如何选择JDK原生还是CGLIB动态代理

在实际应用中,选择JDK的动态代理还是CGLIB的动态代理取决于具体的需求和上下文。例如,如果目标对象已经有一个明确的接口定义,那么使用JDK的动态代理可能更加合适。而如果需要更灵活的字节码操作,或者目标对象没有明确的接口,那么CGLIB可能是一个更好的选择。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
打赏
0
0
0
0
379
分享
相关文章
Java的动态代理
Java动态代理是一种强大的机制,允许在运行时创建接口的代理实例,并拦截方法调用。其核心组件包括`java.lang.reflect.Proxy`和`java.lang.reflect.InvocationHandler`。通过自定义接口、实现接口、编写`InvocationHandler`处理器并生成代理对象,可以灵活地增强方法功能,如日志记录、事务管理等。典型应用场景包括AOP、RPC、延迟加载和Mock测试。与CGLIB相比,JDK动态代理基于接口,性能稍慢但无需第三方库,适用于需要无侵入式增强的场合。
智慧产科一体化管理平台源码,基于Java,Vue,ElementUI技术开发,二开快捷
智慧产科一体化管理平台覆盖从备孕到产后42天的全流程管理,构建科室协同、医患沟通及智能设备互联平台。通过移动端扫码建卡、自助报道、智能采集数据等手段优化就诊流程,提升孕妇就诊体验,并实现高危孕产妇五色管理和孕妇学校三位一体化管理,全面提升妇幼健康宣教质量。
58 12
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
CRaC技术助力ACS上的Java应用启动加速
容器计算服务借助ACS的柔性算力特性并搭配CRaC技术极致地提升Java类应用的启动速度。
探索Java动态代理的奥秘:JDK vs CGLIB
动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
61 0
探索Java动态代理的奥秘:JDK vs CGLIB
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
3021 2
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
127 7
|
1月前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
172 60
【Java并发】【线程池】带你从0-1入门线程池
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
72 23
|
28天前
|
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
100 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码