7.代理模式(Proxy Pattern)

news2024/12/26 18:01:23

古朗月行

  • 代理模式
    • JDK动态代理
      • 代码示例
      • 原码分析
    • cglib动态代理
      • 代码示例
      • 源码分析
  • JDK cglib动态代理对比
  • ClassLoader
    • 类的生命周期:
  • 参考资料

唐 李白

小时不识月,呼作白玉盘。

又疑瑶台镜,飞在青云端。

仙人垂两足,桂树何团团。

白兔捣药成,问言与谁餐?

蟾蜍蚀圆影,大明夜已残。

羿昔落九乌,天人清且安。

阴精此沦惑,去去不足观。

忧来其如何,凄怆摧心肝

代理模式

  In computer programming, the proxy pattern is a software design pattern. A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate. In short, a proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. In the proxy, extra functionality can be provided, for example caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked. For the client, usage of a proxy object is similar to using the real object, because both implement the same interface.
  在计算机编程中,代理模式是一个软件设计模式。

JDK动态代理

  动态代理的本质就是在运行时动态的生成一个代理类,这个代理类的加载同样遵循JVM类加载机制那一套东西,涉及到动态生成代理类的字节码并将其加载到JVM中。

代码示例

package org.example.proxy.jdk;

public interface IService {
    void myselfMethod();
}

package org.example.proxy.jdk.impl;

import org.example.proxy.jdk.IService;

/**
 * 真的对象服务
 *
 * @author Samson Bruce
 * @since 2024/11/26:11:33
 */
public class RealObjectService implements IService {

    @Override
    public void myselfMethod() {
        System.out.println("RealObjectService test .......");
    }
}
/**
 * 动态代理处理类
 *
 * @author samson bruce
 * @since 2024/11/26:11:34
 * 1.拿到被代理对象的引用,然后获取它的接口
 * 2.jdk代理重新生成一个类,同时实现我们个额的代理对象所实现的接口
 * 3.把被代理对象的引用也拿到了
 * 4.重新动态生成一个class字节码
 * 5.然后编译
 */
@Slf4j
public class DynamicInvocationHandler implements InvocationHandler {
    private Object target;

    public Object getInstance(IService target) throws Exception {
        this.target = target;
        Class<?> clazz = target.getClass();
        log.info("被代理对象的class是:{}", clazz);
        //一旦代理类被加载和链接,就可以使用 newProxyInstance 方法返回的 Constructor 对象来创建代理实例。
        // 这个构造器的 newInstance 方法接收一个 InvocationHandler 实例作为参数,并返回代理对象。
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("开始进行方法增强,(记录日志、校验、性能统计等)……");
        Object result = method.invoke(target, args);
        log.info("方法增强完毕,(记录日志、校验、性能统计等)……");
        return result;
    }
}
package org.example.proxy.jdk;


import org.example.proxy.jdk.impl.RealObjectService;

import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @FileName: MainTest
 * @Description:
 * @Author: liulianglin
 * @Date: 2024/11/26:12:10
 */
public class JDKProxyExampleMain {

    public static void printClassInfo(Executable[] targets) {
        for (Executable target : targets) {
            // 构造器/方法名称
            String name = target.getName();
            StringBuilder sBuilder = new StringBuilder(name);
            // 拼接左括号
            sBuilder.append('(');
            Class<?>[] clazzParams = target.getParameterTypes();
            // 拼接参数
            for (Class<?> clazzParam : clazzParams) {
                sBuilder.append(clazzParam.getName()).append(',');
            }
            //删除最后一个参数的逗号
            if (clazzParams.length != 0) {
                sBuilder.deleteCharAt(sBuilder.length() - 1);
            }
            //拼接右括号
            sBuilder.append(')');
            //打印 构造器/方法
            System.out.println(sBuilder.toString());
        }
    }

    /**
     * 将动态代理生成的代理类的字节码保存到本地磁盘,方便调试查看
     * @param path
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws IOException
     */
    public static void saveProxyClassFile(String path)  {

        Class cl = null;
        FileOutputStream fileOutputStream = null;
        try {
            // Java11开始ProxyGenerator,不再public,改为了private,无法直接使用,所以采用反射的方式获取它
            cl = Class.forName("java.lang.reflect.ProxyGenerator");

            Method m =cl.getDeclaredMethod("generateProxyClass",String.class,Class[].class);
            m.setAccessible(true);
            byte[] $proxy1 = (byte[]) m.invoke(null, "$proxy1", RealObjectService.class.getInterfaces());
            System.out.println($proxy1.length);

            fileOutputStream = new FileOutputStream((path + "$Proxy.class"));
            fileOutputStream.write($proxy1);
        }catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (fileOutputStream !=null) {
                try {
                    fileOutputStream.flush();
                    fileOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }


    public static void main(String[] args) {
        try {
            IService iService = (IService) new DynamicInvocationHandler().getInstance(new RealObjectService());
            iService.myselfMethod();

            // 将生成的动态代理类的字节码文件保存到本地
             saveProxyClassFile("./");

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

原码分析

  1. 确定类加载器
    首先,Proxy.newProxyInstance 方法接收一个 ClassLoader 参数,这个参数指定了用于加载代理类的类加载器。通常,这个类加载器是被代理对象的类加载器,但也可以是其他任意的类加载器。
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);

        final Class<?> caller = System.getSecurityManager() == null? null: Reflection.getCallerClass();

        /*
         * Look up or generate the designated proxy class and its constructor.
         * Returns the Constructor object of a proxy class that takes a single argument of type InvocationHandler, given a class loader and an array of interfaces. 
         * The returned constructor will have the accessible flag already set.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
    }

Returns the Constructor object of a proxy class that takes a single argument of type InvocationHandler, given a class loader and an array of interfaces.

    private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces)
    {
        // optimization for single interface
        if (interfaces.length == 1) {
        	//如果只有一个接口,那么首先获取该接口
            Class<?> intf = interfaces[0];
            if (caller != null) {
                checkProxyAccess(caller, loader, intf);
            }
            //使用 proxyCache 缓存来获取或生成代理类的构造器。
            //proxyCache 是一个多层次的缓存结构,其中 sub(intf) 获取或创建与接口相关的子缓存,
            //computeIfAbsent 方法确保对于给定的类加载器 loader,只生成一次代理类。
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        } else {
            // interfaces cloned
            final Class<?>[] intfsArray = interfaces.clone();
            if (caller != null) {
                checkProxyAccess(caller, loader, intfsArray);
            }
            final List<Class<?>> intfs = Arrays.asList(intfsArray);
            return proxyCache.sub(intfs).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        }
    }
    private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
                                           Constructor<?> cons,
                                           InvocationHandler h) {
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (caller != null) {
                checkNewProxyPermission(caller, cons.getDeclaringClass());
            }

            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);
            }
        }
    }
  1. 生成代理类的字节码(核心)
    JDK动态代理通过以下关键步骤生成代理类的字节码:
  • 创建代理类名称:代理类的名称是由 “$Proxy” + 一个数字序号 构成的,例如 P r o x y 0 、 Proxy0、 Proxy0Proxy1 等。这个序号是为了保证代理类的名称唯一性。
  • 实现接口:代理类实现了传入 newProxyInstance 方法的所有接口。
  • 添加构造方法:代理类有一个私有的构造方法,它接受一个 InvocationHandler 类型的参数。
  • 生成方法实现:对于每个接口方法,代理类都会生成一个实现,该实现在调用目标方法之前和之后会调用 InvocationHandler 的 invoke 方法。
  • 字节码生成:JDK内部使用 ProxyBuilder 类(在 JDK 内部,不是公开API)来生成代理类的字节码。
  1. 加载代理类(JVM的那一套)
    生成的字节码被传递给类加载器,由类加载器负责将字节码加载到JVM中,创建代理类的 Class 对象。这个过程包括:
  • 定义类:类加载器使用 defineClass 方法将字节码定义为一个类。
  • 链接类:链接过程包括验证字节码、准备类、解析符号引用等步骤。
  • 初始化类:对于非抽象的代理类,JVM会执行类的初始化,包括执行类构造器 ()。
  1. 创建代理实例(使用过程)
    一旦代理类被加载和链接,就可以使用 newProxyInstance 方法返回的 Constructor 对象来创建代理实例。这个构造器的 newInstance 方法接收一个 InvocationHandler 实例作为参数,并返回代理对象。

  2. 方法调用和拦截(使用过程)
    当代理对象的方法被调用时,JVM会跳转到代理类中的方法实现,这些实现会委托给 InvocationHandler 的 invoke 方法来处理。invoke 方法负责执行实际的逻辑,包括调用目标对象的方法和添加额外的处理。

cglib动态代理

代码示例

源码分析

JDK cglib动态代理对比

JDK动态代理和CGLIB动态代理是Java中常用的两种代理技术,它们有以下几个主要区别:

  1. 代理方式的不同
    JDK动态代理:JDK动态代理依赖于接口,它只能对实现了接口的类进行代理。通过 java.lang.reflect.Proxy 类生成代理对象。即被代理类必须实现至少一个接口。
    CGLIB动态代理:CGLIB(Code Generation Library)是通过继承被代理类来创建代理对象的。CGLIB可以对没有实现接口的类进行代理。它使用字节码生成技术,在运行时动态地生成代理类。
  2. 是否需要接口
    JDK动态代理:要求被代理的类必须实现接口,因此不能代理没有实现接口的类。
    CGLIB动态代理:不需要接口,可以代理没有实现接口的类。
  3. 代理类的生成方式
    JDK动态代理:通过 Proxy.newProxyInstance() 方法生成代理类,该方法使用反射机制来创建代理对象。
    CGLIB动态代理:通过继承被代理类,并通过字节码技术生成代理类。CGLIB通过修改类的字节码实现代理,代理类是被继承的。
  4. 性能
    JDK动态代理:因为它使用反射机制,性能相对较低,尤其是在大量调用代理方法时。
    CGLIB动态代理:因为它是通过继承生成代理类,性能通常比JDK动态代理高。
  5. 代理目标的限制
    JDK动态代理:只能代理接口,不能代理类。如果目标类没有接口,JDK动态代理不能使用。
    CGLIB动态代理:能够代理没有接口的类,但它有一些限制。例如,如果目标类的方法是 final 或 private,CGLIB就无法代理该方法,因为CGLIB通过继承生成代理类,而 final 或 private 方法不能被重写。
  6. 使用场景
    JDK动态代理:适用于目标类实现了接口的场景,特别是当接口较多时,JDK动态代理更加清晰和灵活。
    CGLIB动态代理:适用于目标类没有接口或目标类的方法较多且没有接口的场景,CGLIB在这些场景中更具优势。
  7. Spring框架中的使用
    在Spring框架中,Spring默认使用JDK动态代理进行AOP代理(前提是目标类实现了接口)。如果目标类没有实现接口,Spring则使用CGLIB来创建代理对象。

ClassLoader

类加载是Java中将类的字节码读入内存并准备好供程序使用的过程,这个过程由类加载器(Class Loader)负责。

类的生命周期:

  1. 加载 Loading 找到class文件
  2. 链接 Linking 链接是指将编译后生成的一个或多个目标文件(以及所需的库文件)合并为一个可执行文件的过程。这个过程由链接器(Linker)负责执行。
    验证 Verification 检查字节码的正确性和安全性
    准备 Preparation 分配内存并初始化类变量(静态变量)
    解析 Analysis 将符号引用转换为直接引用,解决类间的依赖关系
  3. 初始化 Initialization 执行类的初始化方法,初始化类变量
  4. 使用 Using
  5. 卸载 Unloading

Java中的类加载器有多种类型,包括:

  • 引导类加载器(Bootstrap Class Loader):加载Java核心类库。
  • 扩展类加载器(Extension Class Loader):加载Java扩展库。
  • 应用类加载器(Application Class Loader):加载应用程序的类。
    类加载机制使得Java具有动态性和灵活性,可以在运行时加载类,从而支持多种应用场景。

参考资料

添加链接描述
Spring AOP
【深度思考】聊聊CGLIB动态代理原理

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

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

相关文章

释放超凡性能,打造鸿蒙原生游戏卓越体验

11月26日在华为Mate品牌盛典上&#xff0c;全新Mate70系列及多款全场景新品正式亮相。在游戏领域&#xff0c;HarmonyOS NEXT加持下游戏的性能得到充分释放。HarmonyOS SDK为开发者提供了软硬协同的系统级图形加速解决方案——Graphics Accelerate Kit&#xff08;图形加速服务…

Zynq(6)FATFS读写eMMC

文章目录 1.简介eMMc与FAT2.eMMC与FAT的关系3.添加xilinx库4.用 FATFS完成emmc的读写5.传送门 1.简介eMMc与FAT eMMC (embedded MultiMediaCard) 是一种嵌入式闪存存储解决方案&#xff0c;由NAND闪存和内置的控制器组成&#xff0c;NAND闪存存储数据&#xff0c;而控制器负责…

【Linux】gdb / cgdb 调试 + 进度条

&#x1f33b;个人主页&#xff1a;路飞雪吖~ &#x1f320;专栏&#xff1a;Linux 目录 一、Linux调试器-gdb &#x1f31f;开始使用 &#x1f320;小贴士&#xff1a; &#x1f31f;gdb指令 &#x1f320;小贴士&#xff1a; ✨watch 监视 ✨打条件断点 二、小程序----进…

如何解决maven项目使用Ctrl + /添加注释时的顶格问题

一、问题描述 相信后端开发的程序员一定很熟悉IDEA编译器和Maven脚手架&#xff0c;使用IDEA新建一个Maven工程&#xff0c;通过SpringBoot快速构建Spring项目。在Spring项目pom.xml文件中想添加注释&#xff0c;快捷键Ctrl /&#xff0c;但是总是顶格书写。 想保证缩进统一…

python学习——数据的验证

文章目录 1. str.isdigit()2. str.isnumeric()3. str.isalpha()4. str.isalnum()5. str.islower()6. str.isupper()7. str.istitle()8. str.isspace()实操 以下是Python中字符串数据验证方法的详细解释&#xff1a; 1. str.isdigit() 这个方法用于检查字符串中的所有字符是否都…

基于Springboot+Vue的在线答题闯关系统

基于SpringbootVue的在线答题闯关系统 前言&#xff1a;随着在线教育的快速发展&#xff0c;传统的教育模式逐渐向互联网教育模式转型。在线答题系统作为其中的一个重要组成部分&#xff0c;能够帮助用户通过互动式的学习方式提升知识掌握度。本文基于Spring Boot和Vue.js框架&…

Web(CSS+JS+HTML实现简单界面)

前言 写的是个人博客界面&#xff0c;代码比较冗余&#xff0c;web的一个小作业。。。。。。 因为后面改了一次&#xff0c;有些css是直接写到了html文件中&#xff0c;重复的代码也比较多。 项目结构 CSS style.css * {margin: 0;padding: 0;box-sizing: border-box; }b…

Scala:正则表达式

object test03 {//正则表达式def main(args: Array[String]): Unit {//定义一个正则表达式//1.[ab]:表示匹配一个字符&#xff0c;或者是a&#xff0c;或者是b//2.[a-z]:表示从a到z的26个字母中的任意一个//3.[A-Z]:表示从A到Z的26个字母中的任意一个//4.[0-9]:表示从0到9的10…

经验帖 | Matlab安装成功后打不开的解决方法

最近在安装Matlab2023时遇到了一个问题&#xff1a; 按照网上的安装教程成功安装 在打开软件时 界面闪一下就消失 无法打开 但是 任务管理器显示matlab在运行中 解决方法如下&#xff1a; matlab快捷方式–>右键打开属性–>目标 填写许可证文件路径 D:\MATLAB\MatlabR20…

VCU——matlab/simulink软件建模

一、认识MATLAB/Simulink 1. matlab主界面 2. simulink 二、Simulink 建模基础 1. Simulink模块 2. 模型的仿真 matlab 中比较两个浮点型&#xff0c;不要用&#xff0c;采取差值和Compare To Constant的方案 3. 自动代码生成

(软件测试文档大全)测试计划,测试报告,测试方案,压力测试报告,性能测试,等保测评,安全扫描测试,日常运维检查测试,功能测试等全下载

1. 引言 1.1. 编写目的 1.2. 项目背景 1.3. 读者对象 1.4. 参考资料 1.5. 术语与缩略语 2. 测试策略 2.1. 测试完成标准 2.2. 测试类型 2.2.1. 功能测试 2.2.2. 性能测试 2.2.3. 安全性与访问控制测试 2.3. 测试工具 3. 测试技术 4. 测试资源 4.1. 人员安排 4.2. 测试环境 4.2.…

Crash-SQLiteDiskIOException

目录 相关问题 日志信息 可能原因 问题排查 相关问题 蓝牙wifi无法使用 日志信息 可能原因 磁盘空间不足&#xff1a;当设备上的可用存储空间不足时&#xff0c;SQLite无法完成磁盘I/O操作&#xff0c;从而导致SQLiteDiskIOException。 数据库文件损坏&#xff1a;如果数…

PyTorch 深度学习框架简介:灵活、高效的 AI 开发工具

PyTorch 深度学习框架简介&#xff1a;灵活、高效的 AI 开发工具 PyTorch 作为一个深度学习框架&#xff0c;以其灵活性、可扩展性和高效性广受欢迎。无论是在研究领域进行创新实验&#xff0c;还是在工业界构建生产级的深度学习模型&#xff0c;PyTorch 都能提供所需的工具和…

Java Web 4 Maven

本文详细介绍了Maven的用途&#xff0c;包括依赖管理、项目结构统一和构建流程标准化&#xff1b;然后讲解了Maven的安装、IDEA中的集成以及依赖管理的核心概念。 1 什么是Maven&#xff1f; 什么是apache&#xff1f; 2 Maven的作用 &#xff08;1&#xff09;方便依赖管理 有…

无人机吊舱类型详解!

一、侦察与监测类吊舱 电子侦察吊舱 功能&#xff1a;主要用于侦察和监测目标&#xff0c;具备侦察、监听、干扰等多种功能。 设备&#xff1a;通常安装有电子侦察设备和通信设备&#xff0c;可以实时获取目标的电子信息&#xff0c;并将数据传输回地面指挥中心。 应用&…

数据结构与算法之美:顺序表详解

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《题海拾贝》、《编程之路》、《数据结构与算法之美》 欢迎点赞、关注&#xff01; 1、 什么…

Neo4j 图数据库安装与操作指南(以mac为例)

目录 一、安装前提条件 1.1 Java环境 1.2 Homebrew&#xff08;可选&#xff09; 二、下载并安装Neo4j 2.1 从官方网站下载 2.1.1 访问Neo4j的官方网站 2.1.2 使用Homebrew安装 三、配置Neo4j 3.1 设置环境变量(可选) 3.2 打开配置文件(bash_profile) 3.2.1 打开终端…

Linux centOS 7 安装 rabbitMQ

1.安装前需要了解&#xff0c;rabbitmq安装需要先安装erlang&#xff0c;特别注意的是erlang与rabbitmq的版本之间需要匹配。 el/7/rabbitmq-server-3.10.0-1.el7.noarch.rpm - rabbitmq/rabbitmq-server packagecloud 3.10版本的rabbitmq 对于erlang的版本要求可以看此连接…

SpringBoot整合Mockito进行单元测试超全详细教程 JUnit断言 Mockito 单元测试

Mock概念 Mock叫做模拟对象&#xff0c;即用来模拟未被实现的对象可以预先定义这个对象在特定调用时的行为&#xff08;例如返回值或抛出异常&#xff09;&#xff0c;从而模拟不同的系统状态。 导入Mock依赖 pom文件中引入springboot测试依赖&#xff0c;spring-boot-start…

QT 中 sqlite 数据库使用

一、前提 --pro文件添加sql模块QT core gui sql二、使用 说明 --用于与数据库建立连接QSqlDatabase--执行各种sql语句QSqlQuery--提供数据库特定的错误信息QSqlError查看qt支持的驱动 QStringList list QSqlDatabase::drivers();qDebug()<<list;连接 sqlite3 数据库 …