Java从入门到精通(十二)~ 动态代理

news2025/2/24 16:23:44

 晚上好,愿这深深的夜色给你带来安宁,让温馨的夜晚抚平你一天的疲惫,美好的梦想在这个寂静的夜晚悄悄成长。

文章目录

目录

前言

主要作用和功能:

应用场景:

二、代理概念

1.静态代理

2.动态代理

2.1 概念介绍

代理对象真实的样子

2.2 代码实现

接口规范:

 真实对象

 代理对象:

测试类: 

测试结果:​编辑 

2.3.  流程分析 

1.main方法中调用createProxy(new ArrayList())将真实对象传给代理工具类进行代理。

2.代理工具类接收到真实对象,会调用newProxyInstance()通过反射创建对应的代理对象。

3. 底层根据三个参数创建完,代理对象后,会将其返回。

4. 执行listProxy.containsAll(targetList); 会跳转到代理对象,通过透传调用InvocationHandler接口的invoke()方法,并将接口反射到对应的containsAll()方法的信息传递传递过去。


前言

无反射无Java,无动态代理无框架。

  1. 动态代理可以帮助在不修改原始类代码的情况下,对原始类的方法进行增强、添加额外的处理逻辑或者拦截某些操作。 
  2. 动态代理实现的关键在于利用反射动态生成代理类或者代理对象,这些代理对象可以拦截对真实对象方法的调用,并在调用前后执行额外的逻辑。
  3. 动态代理是一种在运行时动态生成代理类的技术,它允许在不事先创建实际实现类的情况下,创建一个实现了特定接口或一组接口的代理类。


一、什么是类加载器?

类加载器(Class Loader)在Java中是一个重要的运行时系统组件,负责将字节码文件加载到内存中,并生成对应的类对象。在Java虚拟机(JVM)中,每个类都需要在运行时动态加载到内存中才能被使用,而类加载器就是完成这个任务的核心。

主要作用和功能:

  1. 加载类文件: 类加载器负责从文件系统、JAR文件、网络中加载.class文件,并将其转换为一个Class对象。

  2. 类的唯一性: 类加载器确保每个类只被加载一次,即使多次请求加载同一个类,也只会得到同一个Class对象的引用。

  3. 双亲委派模型: Java的类加载器采用双亲委派模型(Parent Delegation Model),即除了顶层的启动类加载器外,其余类加载器都有父类加载器。当一个类加载器收到类加载请求时,它会先委托给父类加载器进行加载,只有在父类加载器无法加载时,才会自己尝试加载。

  4. 安全性: 类加载器也参与了Java安全模型的实现,例如沙箱安全性。

应用场景:

类加载器的灵活性使得Java具有了丰富的动态性和扩展性,例如在Web容器中,每个Web应用程序都有独立的类加载器实例,可以隔离不同应用程序的类加载,避免冲突。在框架和插件化系统中,类加载器的使用也十分普遍,可以动态加载和卸载模块。

二、代理概念

在代理模式中,有三个主要角色:

  • 抽象对象(Subject):定义了真实对象和代理对象的公共接口,客户端通过这个接口访问真实对象或代理对象。
  • 真实对象(Real Subject):实现了抽象角色定义的具体业务逻辑,是代理模式中被代理的对象。
  • 代理对象(Proxy):持有对真实对象的引用,并实现了抽象角色定义的接口,可以在调用真实对象之前或之后执行额外的操作。

1.静态代理

继承实际上就是静态代理,通过父类应用调用子类重写的方法,子类对父类进行了代理,这种代理是在运行前就生成的字节码,但这种存在问题,真实代码如果进行增加删除修改,子类也需要,维护成本就比较高。

// 定义接口 Person
interface Person {
    void doWork();
}

// 实现接口的学生类
class Student implements Person {
    @Override
    public void doWork() {
        System.out.println("学习");
    }
}

// 静态代理类实现接口 Person
class StaticProxy implements Person {
    private Person person;

    public StaticProxy(Person person) {
        this.person = person;
    }

    @Override
    public void doWork() {
        System.out.println("doSomething ----- start");
        person.doWork();
        System.out.println("doSomething ----- end");
    }
}

// 测试类
public class ProxyDemo {
    public static void main(String[] args) {
        // 创建一个学生对象
        Person student = new Student();
        
        // 创建静态代理对象,将学生对象作为参数传入
        StaticProxy staticProxy = new StaticProxy(student);
        
        // 调用静态代理对象的 doWork 方法
        staticProxy.doWork();
    }
}

其实静态代理也是很繁琐的,我们需要为每一个类,每一个方法添加相应的操作。 

上面的代码我们不难发现 静态代理 存在一个问题,代理主题类与真实主题类之间的 耦合程度太高 ,当真实主题类中增加、删除、修改方法后,那么代理主题类中的也必须增加、删除、修改相应的方法,提高了代码的维护成本。另一个问题就是当代理对象代理多个 Subject 的实现类时,多个实现类中必然出在不同的方法,由于代理对象要实现与目标对象一致的接口(其实是包含关系),必然需要编写众多的方法,极其容易造成臃肿且难以维护的代码。

2.动态代理

2.1 概念介绍

动态代理是在运行时动态生成的类或者对象,而不是编译时静态确定的类。这与传统的继承关系有所不同,传统继承是在编译时确定的静态关系。

  1. 代理类:代理对象和真实对象实现同一个接口,其中代理对象包含真实对象的所有方法,然后对真实对象的所有方法的调用的时候,通通先去调用代理类的Invocation handler。所有方法都走我的代理类,那我就可以在我的代理类去做任何我想做的逻辑了。
  2. Invocation handler是一个增强器,proxy是一个调度器:你去找的时候总会找一个为你服务。
  3. 那其实这是最核心的呢就是通过我们的Porxy.newProxyInstance方法去生成我们的动态代理类以及访问它的实例。 

当客户端调用代理对象的方法时,实际上是调用了 InvocationHandler 的 invoke 方法,传递了代理对象、方法和参数等信息。当代理对象调用任何方法时,都会回调这个方法,在动态代理模式中,代理对象会在运行时将方法调用转发给实现了InvocationHandler接口的处理器对象。代理对象在调用方法时,实际上是在"回调"(调用)InvocationHandler接口的方法,将控制权交给处理器对象。时机:在调用被代理对象的方法的时候,触发:会自动回调invoke()方法。

代理对象真实的样子

设置生成的动态代理的代码生成到指定路径,方便我们查看。

其中super.h:表示父类Proxy 类的成员变量InvocationHandler在newProxyInstance创建代理对象的时候,将参数三匿名内部类的地址值传递给h,因此调用super.h也就是你传的参数,然后将m3通过反射获取的接口中对应类的save()方法的信息。

2.2 代码实现

接口规范:
package proxy;

/**
 * @author windStop
 * @version 1.0
 * @description user业务层
 * @date 2024年07月27日23:37:55
 */
public interface UserService {
    boolean login(String name,String password) throws InterruptedException;

    String selectUsers(int id) throws InterruptedException;

    boolean deleteUsers(int id) throws InterruptedException;
}
 真实对象
package proxy;

/**
 * @author windStop
 * @version 1.0
 * @description User业务层实现类
 * @date 2024年07月27日23:40:01
 */
public class UserServiceImpl implements UserService{

    @Override
    public boolean login(String name, String password) throws InterruptedException {
        boolean flag = false;
        if (name.equals("windStop") && password.equals("123456")){
            flag = true;
        }
        Thread.sleep(3000);
        return flag;
    }

    @Override
    public String selectUsers(int id) throws InterruptedException {
        System.out.println("查询成功");
        Thread.sleep(200);
        return "查询成功";
    }

    @Override
    public boolean deleteUsers(int id) throws InterruptedException {
        System.out.println("删除成功");
        Thread.sleep(200);
        return true;
    }
}
 代理对象:
package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author windStop
 * @version 1.0
 * @description 代理工具类
 * @date 2024年07月27日23:37:01
 */
public class ProxyUtil {
    //ClassLoader: 用于定义代理类的类加载器。动态代理需要在运行时生成代理类的字节码,
    //因此需要指定一个类加载器来加载这些类。通常使用当前类的类加载器来加载。
    public static UserService createProxy(UserService userService){
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),
                new Class[]{UserService.class},
                //将方法调用分派给的调用处理程序
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if (method.getName().equals("login") || method.getName().equals("selectUsers")
                        || method.getName().equals("deleteUsers")){
                            long startTime = System.currentTimeMillis();
                            Object rs = method.invoke(userService, args);
                            long endTime = System.currentTimeMillis();
                            System.out.println(method.getName() + "方法执行了" + (endTime - startTime) + "ms");
                            return rs;
                        }else{
                            return method.invoke(userService,args);
                        }
                    }
                }
        );
        return userServiceProxy;
    }
}
测试类: 
package proxy;

/**
 * @author windStop
 * @version 1.0
 * @description 测试动态代理
 * @date 2024年07月27日23:31:58
 */
public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        UserService userServiceProxy = ProxyUtil.createProxy(new UserServiceImpl());
        userServiceProxy.deleteUsers(10);
        userServiceProxy.selectUsers(20);
        userServiceProxy.login("xiaoming","123");
    }
}
测试结果:

2.3.  流程分析 

List listProxy = ProxyUtil.createProxy(new ArrayList<>());
List<String> targetList = new ArrayList<>();
targetList.add("item1");
targetList.add("item2");
listProxy.containsAll(targetList);

通过上述代码,我们可以看出:我们并没有在真实对象中进行时间计算,但结果会给我返回该流程所执行的时间,这就是动态代理的核心作用,在没有惊动我真实对象的代码前提下,动态的给其进行添加功能。

1.main方法中调用createProxy(new ArrayList())将真实对象传给代理工具类进行代理。
2.代理工具类接收到真实对象,会调用newProxyInstance()通过反射创建对应的代理对象。

参数一:用于定义代理类的类加载器。这里需要类加载器的原因是: 

动态代理需要显式提供类加载器的原因是因为它们生成的代理类不是预先存在的,而是在运行时动态生成的。为了确保这些动态生成的代理类能够被正确加载和使用,需要通过 Proxy.newProxyInstance 方法显式指定一个合适的类加载器来加载这些类。通常使用当前类的类加载器来加载。

参数二:提供定义规范的接口。

为什么要定义抽象对象(接口)呢?

  1. 类型匹配和约束

    • 接口定义了被代理对象和代理对象必须遵循的契约。动态代理根据接口来生成代理类,确保代理对象可以替代真实对象。这种约束保证了代理对象可以正确地提供和被代理对象相同的方法和行为。
  2. 实现多态

    • 接口使得代理对象在运行时可以动态替代被代理对象,实现了多态性。客户端代码通过统一的接口调用代理对象和被代理对象的方法,而不需要关心具体是哪个对象在处理请求。这种特性使得代码更加灵活和可扩展。
  3. 动态生成代理类

    • 动态代理技术通常在运行时生成代理类,这与静态代理不同,静态代理是在编译期间就已经确定了代理类的实现。基于接口的代理可以根据接口定义灵活地生成代理对象的代码,这种动态生成的能力使得代理对象的行为可以在运行时根据需要进行适配和修改。
  4. 解耦合

    • 接口定义了被代理对象的行为和代理对象提供的功能,从而实现了解耦合。代理对象可以在不影响客户端代码的情况下替换被代理对象,这种灵活性和解耦合的设计符合面向对象设计的依赖倒置原则和单一职责原则。客户端只需要依赖于接口,而不需要关心具体的实现类,从而提高了代码的灵活性和可维护性。
  5. 面向接口编程:可以利用动态代理,获取出接口的方法,然后通过代理对象调用该方法,就是多态的情景了,子类对象调用父类方法(调用的实际是被虚方法表覆盖的方法)。如果代理对象没有继承该接口,就无法调用接口中反射的方法,就会抛出IllegalAccessException 异常。

总之,接口在动态代理中的应用,不仅限于定义契约和约束,还包括实现多态、动态生成代理类以及实现解耦合等重要作用,这些特性使得动态代理在实际应用中具有广泛的适用性和灵活性。

 参数三:调用处理器,当用户使用代理对象调用了方法后,通通先去调用我的InvocationHandler去做任何我想做的逻辑了 。

3. 底层根据三个参数创建完,代理对象后,会将其返回。

创建出对象的样子:

继承抽取出的接口规范接口,生成该接口的所有方法的属性。

通过反射对这些(方法类型)的成员变量赋值。

4. 执行listProxy.containsAll(targetList); 会跳转到代理对象,通过透传调用InvocationHandler接口的invoke()方法,并将接口反射到对应的containsAll()方法的信息传递过去。

注意:其中super.h:表示父类Proxy 类的成员变量InvocationHandler

最后执行invoke()代码的逻辑

  1. proxy:用来调用代理对象自身的其他方法;
  2. method:用来获取被调用方法的详细信息;
  3. args:则是被调用方法的具体参数值。
  4. 返回值:invoke 方法的返回值是被代理方法的返回值。这里返回的对象是实际被代理方法执行后的结果。

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

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

相关文章

MSPM0G3507将系统时钟改为80M

这款板卡手册上写着最高80M的主频&#xff0c;不过要达到80M需要自己配置时钟树分频倍频。 第一步&#xff1a;打开SysConfig工具&#xff0c;那个方框原本是没有勾选的&#xff0c;这里我们把它勾选 第二步&#xff1a; 点击时钟树配置 第三步&#xff1a; 主要是改变这几个…

nodejs - MongoDB 学习笔记

一、简介 1、MongoDB 是什么 MongoDB 是一个基于分布式文件存储的数据库&#xff0c;官方地址 https://www.mongodb.com/ 2、数据看是什么 数据库&#xff08;DataBase&#xff09;是按照数据结构来组织、存储和管理数据的应用程序。 3、数据库的作用 主要作用是 管理数据…

docker命令大全(新手必备)-my write

一、命令图谱 二、基本命令 docker version #显示版本信息 docker info #显示系统信息,包括镜像、容器数量 docker 命令 --help #帮助 三、镜像命令 3.1 docker images 查看本地主机上的镜像 docker images [OPTIONS] [REPOSITORY[:TAG]] 选项:-a …

【JavaEE初阶】Thread类及常见方法

目录 &#x1f4d5; Thread类的概念 &#x1f4d5; Thread 的常见构造方法 &#x1f4d5; Thread 的几个常见属性 &#x1f4d5; start()-启动一个线程 &#x1f4d5; 中断一个线程 &#x1f6a9; 实例一 &#x1f6a9; 实例二 &#x1f6a9; 实例三 &#x1f4d5; jo…

Pytorch深度学习实践(8)多分类任务

多分类问题 多分类问题主要是利用了Softmax分类器&#xff0c;数据集采用MNIST手写数据集 设计方法&#xff1a; 把每一个类别看成一个二分类的问题&#xff0c;分别输出10个概率 但是这种方法存在一种问题&#xff1a;不存在抑制问题&#xff0c;即按照常规来讲&#xff0c…

Python的文件操作介绍

一、编码格式介绍 1.1、常见的字符编码格式 1.2、Python字符编码格式 Python的解释器使用的是Unicode&#xff08;内存&#xff09; .py文件在磁盘上使用UTF-8存储&#xff08;外存&#xff09; #encodinggbkprint(你好&#xff0c;中国) 二、文件的读写原理 2.1、文件的…

Servlet1-Servlet程序、请求处理、继承体系

目录 什么是Servlet 手动实现Servlet程序 ​编辑url地址如何定位到Servlet程序去访问 Servlet的生命周期 ​编辑GET和POST请求的分发处理 通过继承HttpServlet类实现Servlet程序 IDEA菜单生成Servlet程序 Servlet类的继承体系 ServletConfig类 ServletContext类 什么…

Docker学习与实战

一、Docker安装 移除旧版本docker sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine配置docker yum源 sudo yum install -y yum-utils配置阿里云docker仓库 sudo y…

甄选范文“论软件测试中缺陷管理及其应用”软考高级论文,系统架构设计师论文

论文真题 软件缺陷指的是计算机软件或程序中存在的某种破坏正常运行能力的问题、错误,或者隐藏的功能缺陷。缺陷的存在会导致软件产品在某种程度上不能满足用户的需要。在目前的软件开发过程中,缺陷是不可避免的。软件测试是发现缺陷的主要手段,其核心目标就是尽可能多地找…

Nvidia GPU驱动安装报错显卡与驱动不兼容(本身兼容)

最近在公司服务器上遇到了一个特别离谱的问题&#xff0c;就是在本身在nividia官网上面下载的匹配的显卡驱动&#xff0c;安装之后采用下面命令查看驱动显示&#xff1a; $ nvidia-smiNVIDIA-SMI has failed because it couldnt communicate with the NVIDIA driver. Make su…

exe4j 使用jar包 打包exe程序,并且自带jre

1. 准备工作 1) 程序jar包一个 2) jdk自带jre文件夹一个 3&#xff09;exe4j 程序 &#xff0c;自行搜索 我用的5.1 4&#xff09;图标&#xff08;icon&#xff09;文件一个&#xff0c;用来作为exe程序的图标 5&#xff09; 图片&#xff08;png/jpg等&#xff09;用来打开…

LeetCode 118.杨辉三角 C++写法

LeetCode 118.杨辉三角 C写法 思路&#x1f9d0;&#xff1a; 我们使用vector来进行解答&#xff0c;该题规律简单&#xff0c;观察一下就可以发现&#xff0c;下一行的非1部分是上一行相同下标与上一行相同下标的前一个数据的和。难一点的是数据存储方式&#xff0c;C语言需要…

相机怎么选(不推荐,只分析)

title: 相机怎么选 tags: [相机, 单反相机] categories: [其他, 相机] 最近准备购买&#xff0c;相机怎么选&#xff0c;我去搜索了许多文章&#xff0c;整理了一篇小白挑选技术篇&#xff0c;供大家参考。 分类 胶片相机 需要装入胶卷才能使用的相机&#xff0c;拍照后可直…

【网络安全】子域名模糊测试实现RCE

未经许可&#xff0c;不得转载。 文章目录 正文总结 正文 在之前测试一个私人项目时&#xff0c;我报告了admin.Target.com上的Auth Bypass漏洞&#xff0c;这将导致SQLI&RCE &#xff0c;该漏洞在报告后仅一天就被修复。 现在重拾该应用程序&#xff0c;对子域进行模糊测…

安德森四原则:从对称性破缺到复杂性涌现

中国物理学会期刊网 2023年06月20日 10:00 北京 以下文章来源于集智俱乐部&#xff0c;作者 Krakauer 导语 对称性破缺与复杂性的涌现有何关系&#xff1f;在几十年的研究进程中&#xff0c;诺奖得主、圣塔菲研究所发起人菲利普安德森&#xff08;Philip Anderson&#xff09…

十四、【Python】基础教程-【Python全掌握】六大基础数据类型:字典(dict)类型的终极指南

目录 一、字典&#xff08;dict&#xff09; 1. 创建字典 2. 访问元素 3. 添加元素 4. 修改元素 5. 删除元素 6. 检查键是否存在 7. 获取字典的键、值、项 8. 遍历字典 9. 更新字典 10. 复制字典 11. 字典推导式 12. 获取默认值 13. 弹出元素 14. 清空字典 15. …

第15周 Zookeeper分布式锁与变种多级缓存

Zookeeper **************************************************************

Verilog语言和C语言的本质区别是什么?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C语言的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 用老石的一句话其实很好说…

前端八股文 promise async await 的理解

promise是什么 Promise 是异步编程的一种解决方案&#xff0c;比传统的解决方案——回调函数和事件——更合理和更强大。 目的 解析 吴优编程 &#xff08;解决异步编程中的嵌套问题的&#xff0c;将嵌套的格式 用peomise 写成同步&#xff09; promise.then() 是成功后继…

【人工智能】人工智能概论(一):人工智能基本概概念、学派、发展历程与新一代人工智能

文章目录 1. 人工智能的基本概念与定义2. 人工智能的主要学派及主旨思想2.1. 符号主义学派&#xff1a;AI源自数学逻辑2.2. 连接主义学派&#xff1a;AI源自仿生学2.3. 行为主义学派&#xff1a;AI源自控制论 3. 人工智能的起源及发展历程4. 驱动新一代人工智能快速发展的因素 …