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

news2024/11/23 15:26:02

攻破Java技术盲点之剖析动态代理的实现原理和开发指南

  • 前提介绍
  • 技术回顾
    • 回顾问题分析
      • 代理对象实现了什么接口
      • 代理对象的方法体是什么
  • CGLIB动态代理
    • CGLIB的原理
        • 继承方式
    • 为什么要用CGLIB
      • 建立被代理的类
      • cglib拦截器类
      • 测试类
        • 易错点:CGLIB的invoke和invokeSuper的区分
          • invoke方法
          • invokeSuper方法:
      • TargetObject代理对象的类型信息
        • final控制以及限制
  • 未完待续
  • 归纳总结
    • JDK的动态代理
    • CGLIB的动态代理
    • 如何选择JDK原生还是CGLIB动态代理

前提介绍

经历了上一篇文章内容:《精彩推荐 |【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");  
    }  
}  

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();  
    }  
 
}  

上述代码中,我们通过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;  
   }  

通过调用 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();  
    }  
}  

我们分析了一下,从文件数上来说,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
  • 看到使用CGLIB代理之后的对象类型是cglib.TargetObject E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIBe3734e52,这是CGLIB动态生成的类型;父类是TargetObject,印证了CGLIB是通过继承实现代理;

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

final控制以及限制

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

java.lang.IllegalArgumentException: Cannot subclass final class cglib.TargetObject

同样的,final方法是不能重载的,所以也不能通过CGLIB代理,遇到这种情况不会抛异常,而是会跳过final方法只代理其他方法。如果你还对代理类cglib.TargetObject E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIBe3734e52具体实现感兴趣,它大致长这个样子:

CGLIB代理类具体实现

public class TargetObject$$EnhancerByCGLIB$$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();
  }
  ...
}

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

未完待续

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


归纳总结

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

JDK的动态代理

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

CGLIB的动态代理

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1370128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【2024最新-python3小白零基础入门】No1.python简介以及环境搭建

文章目录 一 python3 简介二 python语言的特点三 python安装四 安装开发工具-pycharm五 新建一个python项目1.新建项目2 配置虚拟环境3 运行项目 一 python3 简介 Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性&a…

系分笔记数据库技术之数据库安全措施

文章目录 1、概要2、数据库的保护措施3、数据库的故障4、备份和日志5、总结 1、概要 数据库设计是考试重点,常考和必考内容,本篇主要记录了知识点:数据库故障及解决、数据库安全保护措施和数据库备份及恢复。 2、数据库的保护措施 数据库安全…

学习笔记——C++二维数组

二维数组定义的四种方式: 1,数据类型 数组名[ 行数 ][ 列数 ]; 2,数据类型 数组名[ 行数 ][ 列数 ]{{数据1,数据2},{数据3,数据4}}; 3,数据类型 数组名[ 行数…

LeetCode(242)有效的字母异位词⭐

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。 示例 1: 输入: s "anagram", t "nagaram" 输出: true示例 2: 输…

2024年第九届图像、视觉与计算国际会议(ICIVC 2024)即将召开!

2024年第九届图像、视觉与计算国际会议(ICIVC 2024)将于2024年7月15-17日在中国苏州举行,本次会议是由昆山杜克大学和IEEE联合主办,SMC技术支持,西安科技大学,北京工业大学,中国海洋大学&#x…

5分钟彻底搞懂什么是token

大家好啊,我是董董灿。 几年前在一次工作中,第一次接触到自然语言处理模型 BERT。 当时在评估这个模型的性能时,领导说这个模型的性能需要达到了 200 token 每秒,虽然知道这是一个性能指标,但是对 token 这个概念却不…

Qt / day01

1. 思维导图 2. 自由发挥应用场景实现一个登录窗口界面。 代码(mywidget.cpp): #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {// windows setup //setup windows sizethis->resize(600, 370);//set window fixed si…

c++实现支持动态扩容的栈(stack)

1.在栈容量满时自动扩容: 支持自动扩容栈实现: // // myStack.hpp // algo_demo // // Created by Hacker X on 2024/1/9. //#ifndef myStack_hpp #define myStack_hpp #include <stdio.h> #include <string.h> //栈实现 //1.入栈 //2.出栈 //3.空栈 //4.满栈 …

图片纹理贴图

/* * 当需要给图形赋予真实颜色的时候&#xff0c;不太可能为没一个顶点指定一个颜色&#xff0c;通常会采用纹理贴图 * 每个顶点关联一个纹理坐标 (Texture Coordinate) 其它片段上进行片段插值 * */#include <iostream> #define STBI_NO_SIMD #define STB_IMAGE_IMPLE…

LeetCode刷题--- 下降路径最小和

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 ​​​​​​http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述动…

服务器迁移上云

一、服务器迁移上云 1、服务器迁移概念&#xff1a; 服务器迁移一般来说是将物理服务器从一个地点&#xff08;物理机房&#xff09;移动到另一个地点&#xff0c;或将数据从一台服务器移动到另一台服务器的过程。 物理服务器迁移场景&#xff1a; ● 机房搬迁&#xff1a;…

松鼠目标检测数据集VOC格式400张

松鼠&#xff0c;一种小巧玲珑、活泼可爱的啮齿类动物&#xff0c;以其蓬松的大尾巴和机敏的动作而广受欢迎。 松鼠通常体型小巧&#xff0c;四肢灵活&#xff0c;尾巴蓬松。它们的耳朵大而直立&#xff0c;眼睛明亮&#xff0c;给人留下了深刻的印象。松鼠的毛色因种类而异&a…

selenium点击链接下载文件,并获取文件

在自动化测试时&#xff0c;有时我们会需要自动化获取下载的文件&#xff0c;这是我们要怎么办呢&#xff0c;跟着我一步步的来获取下载的文件吧 首先声明下&#xff0c;我们需要引入的类 from selenium import webdriver from selenium.webdriver.chrome.options import Op…

练习-指针笔试题

目录 前言一、一维整型数组1.1 题目一1.2 题目二 二、二维整型数组2.1 题目一2.2 题目二2.3 题目三 三、结构体3.1 题目一&#xff08;32位机器运行&#xff09; 四、字符数组4.1 题目一4.2 题目二 总结 前言 本篇文章记录关于C语言指针笔试题的介绍。 一、一维整型数组 1.1 …

使用vue实现一个网页的贴边组件。

使用vue实现一个网页的贴边组件。 先来看效果&#xff1a; 2024-01-04 10.46.22 https://www.haolu.com/share/V00O6HWYR8/36207fc21c35b2a8e09bf22787a81527 下面是具体代码实现&#xff1a; 1、父组件。&#xff08;用于贴边展示的组件&#xff09; <template>&…

[NISACTF 2022]midlevel

[NISACTF 2022]midlevel wp 信息搜集 进入页面&#xff0c;右上角显示了我的真实 IP &#xff1a; 最下面提示&#xff1a;Build With Smarty ! &#xff1a; Smarty 是 PHP 的模板引擎&#xff0c;判断为 Smarty 模板注入。 Smarty 模板注入 推荐博客&#xff1a;Smarty…

uni-app中轮播图实现大图预览

参考效果 当轮播图滑动切换的时候更新自定义下标&#xff0c;当图片被点击的时候大图预览。 参考代码 商品详情页轮播图交互 <script setup lang"ts"> // 轮播图变化时 const currentIndex ref(0) const onChange: UniHelper.SwiperOnChange (ev) > …

SSH远程访问出现Permission denied(password)解决方法

首先&#xff0c;这个不是密码输错了的问题&#xff1b; 1、在主机先ping一下服务器 ping XXX.XXX.XX.XXX (服务器ip地址) 如果pin成功了&#xff0c;说明可以进行连接 查看服务器的ip ifconfig2、主机连接服务器 &#xff08;服务器的ip&#xff09; ssh testXXX.XXX.XX.…

CSS3新增边框样式

边框样式 概念:在CSS3中&#xff0c;针对元素边框增加了丰富的修饰属性。 常见的边框样式属性有以下 属性说明border-radius圆角效果box-shadow边框阴影border-image边框背景 border-radius属性 概念&#xff1a;border-radius属性可以为元素添加圆角效果 语法&#xff1…

Spring 基于注解的AOP见解4

5.基于注解的AOP配置 5.1创建工程 5.1.1.pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&…