第06天 静态代理和动态代理

news2024/11/25 2:20:53

每天学习一个知识点

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉
🍎个人主页:Leo的博客
💞当前专栏:每天一个知识点
✨特色专栏: MySQL学习
🥭本文内容:第06天 静态代理和动态代理
🖥️个人小站 :个人博客,欢迎大家访问
📚个人知识库: 知识库,欢迎大家访问

在了解静态代理和动态代理之前,我们需要先了解一下什么是代理模式

1. 代理模式

代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。

  1. 代理模式: 为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能
  2. 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象

举个例子:当我们工作之后需要出去租房子,房东张贴广告带我看房子,最后签合同,但是房东只想坐着签合同并不想到处跑着看房子,于是就找了一个中介专门来宣传广告并且带租户看房子,而房东只负责签合同收钱!中介在这里就可以看作是代理你的代理对象代理的行为(方法)是带租户看房子。

image-20230814112036352

代理模式有静态代理和动态代理两种实现方式,我们 先来看一下静态代理模式的实现。

2.静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。

上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

静态代理实现步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

下面通过代码来进一步了解静态代理的应用

  1. 定义一个卖房的接口

    public interface UserService {
    
         String sell(String message);
    }
    
  2. 实现卖房接口

    public class UserServiceImpl implements UserService{
        public String sell(String message) {
            System.out.println("我是房东-->我要卖房,找我签合同");
            return message;
        }
    }
    
  3. 创建代理类实现卖房接口

    public class UserServiceProxy implements UserService{
    
        private UserService userService;
    
        public UserServiceProxy(UserService userService) {
            this.userService = userService;
        }
    
        @Override
        public String sell(String message) {
            System.out.println("我是中介,我可以带你去看房");
            userService.sell(message);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("after method send()");
            return null;
        }
    }
    
  4. 测试

    @org.junit.Test
        public void name() {
            UserServiceImpl userService = new UserServiceImpl();
            UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
            userServiceProxy.sell("卖房");
        }
    }
    

    控制台打印 :

    image-20230814114502067

静态代理存在的问题

1. 静态类文件数量过多,不利于项目管理
   UserServiceImpl  UserServiceProxy
   OrderServiceImpl OrderServiceProxy
2. 额外功能维护性差
   代理类中 额外功能修改复杂(麻烦)

3. 动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。

从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。

动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理CGLIB 动态代理等等。

概念:通过代理类为原始类(目标类)增加额外功能,代理类由Spring动态生成。
好处:利于原始类(目标类)的维护

代理运行对象在程序运行的过程中动态的在内存进行构建,可以灵活的进行业务功能的切换。

3.1 JDK动态代理

JDK动态代理是基于 Java 的反射机制实现的。使用 JDK中接口和类实现代理对象的动态创建。JDK的动态代理要求目标对象必须实现接口,而代理对象不必实现业务接口,这是 java 设计上的要求。从 jdk1.3 以来,java 语言通过 java.lang.reflect 包提供三个类和接口支持代理模式,它们分别Proxy, Method和 InvocationHandler。

  • 目标对象必须实现业务接口

  • JDK代理代理对象不需要实现业务接口

  • 动态代理的对象在程序运行中不存在

  • 动态代理灵活的进行业务功能的切换

1. Proxy类

​ 通过JDK的 java.lang.reflect.Proxy 类实现动态代理,会使用其静态方法 newProxyInstance(),依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对象。

public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces,InvocationHandler handler)
    
loader:目标类的类加载器,通过目标对象的反射可获取
interfaces:目标类实现的接口数组,通过目标对象的反射可获取
handler:调用处理器。

2. Method类

invoke()方法的第二个参数为 Method 类对象,该类有一个方法也叫 invoke(),可以调用目标方法。这两个 invoke()方法,虽然同名,但无关。

public Object invoke ( Object obj, Object... args)
    
obj:表示目标对象
args:表示目标方法参数,就是其上一层 invoke 方法的第三个参数
该方法的作用是:调用执行 obj 对象所属类的方法,这个方法由其调用者 Method 对象确定。在代码中,一般的写法为
method.invoke(target, args);
其中,method 为上一层 invoke 方法的第二个参数。这样,即可调用了目标类的目标方法。

3. IocationHandler接口

InvocationHandler 接口叫做调用处理器,负责完成调用目标方法,并增强功能。通过代理对象执行目标接口中的方法 , 会把方法的调用分派给调用处理器(InvocationHandler)的实现类,执行实现类中的 invoke()方法,我们需要把功能代理写在 invoke()方法中 。此接口中只有一个方法。

img

invoke 方法中可以截取对目标方法的调用。在这里进行功能增强。Java 的动态代理是建立在反射机制之上的。实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑。这个接口中有一个方法 invoke(),具体加强的代码逻辑就是定义在该方法中的。通过代理对象执行接口中的方法时,会自动调用 invoke()方法。

4. 实现步骤

  1. 代理对象不需要实现接口。

  2. 代理对象的生成是利用JDK中的proxy类,动态的在内存中构建代理对象。

  3. 代码实现接口

  4. ProxyFactory.java 代理实例生成工厂

    package com.leo.demo02.service.impl;
    
    import com.leo.demo02.service.UserService;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * @author : Leo
     * @version 1.0
     * @date 2023/8/14 13:40
     * @description :
     */
    public class ProxyFactory {
    
        /**任何的代理对象,都要清楚目标对象,在此得设置一个目标对象*/
        private UserService userService;
    
        //传入目标对象
    
        public ProxyFactory(UserService userService){
            this.userService = userService;
        }
    
        /** 给目标对象生成代理实例 */
        public Object getProxyInstance(){
            return Proxy.newProxyInstance(
                    //指定当前目标对象,使用类加载器获得
                    userService.getClass().getClassLoader(),
                    //获得目标对象实现的所有接口
                    userService.getClass().getInterfaces(),
                    //处理代理实例上的方法并返回调用结果
                    new InvocationHandler() {
                        @Override
                        public Object invoke(
                                //代理对象的实例
                                Object proxy,
                                //代理的目标对象的实现方法
                                Method method,
                                //代理的目标对象实现方法的参数
                                Object[] args) throws Throwable {
                            System.out.println("我是中介,我在张贴广告......");
                            System.out.println("我是中介,我在带领租客看房子......");
                            //目标对象执行自己的方法
                            Object returnValue = method.invoke(userService, args);
                            System.out.println("我是中介,我在带领租客办理结束流程");
                            return returnValue;
                        }
                    });
        }
    }
    
    1. 测试类

       @org.junit.Test
          public void test03() {
              // 创建代理工厂对象
              ProxyFactory factory = new ProxyFactory(new UserServiceImpl());
              UserService service = (UserService) factory.getProxyInstance();
              service.sell("卖房");
          }
      

      控制台打印 :

      image-20230814135707682

注意:JDK动态代理中,代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用JDK动态代理。

3.2 CGlib动态代理

Code Generation Library ,又称为子类通过动态的的在内存中构建子类对象,重写父类方法进行代理功能的增强。

想要功能扩展,但目标对象没有实现接口,怎样功能扩展?

解决方案:子类的方式

Class subclass extends UserDao{}

以子类的方式实现(cglib代理),在内存中构建一个子类对象从而实现对目标对象功能的扩展。

1. CGlib动态代理的特点

  1. JDK动态代理有一个限制,就是使用动态代理的目标对象必须实现一个或多个接口。如果想代理没有实现类的接口,就可以使用CGLIB进行代理。
  2. CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception。
  3. CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

2. CGLIB的实现步骤

  1. 需要spring-core-5.2.5.jar依赖即可。

  2. 引入功能包后,就可以在内存中动态构建子类

  3. 被代理的类不能为final, 否则报错。

  4. 目标对象的方法如果为final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法。

  5. 编写 ProxyFactory.java

    	package com.leo.demo02.service.impl;
    
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * @author : Leo
     * @version 1.0
     * @date 2023/8/14 14:00
     * @description :
     */
    public class ProxyFactory2 implements MethodInterceptor {
        /** 目标对象 */
        private Object target;
    
        /** 传入目标对象 */
        public ProxyFactory2(Object target){
            this.target = target;
        }
        /** Cglib采用底层的字节码技术,在子类中采用方法拦截的技术,拦截父类指定方法的调用,并顺势植入代理功能的代码 */
        @Override
        public Object intercept(Object obj, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {
            //代理对象的功能
            System.out.println("我是中介,我在张贴广告......");
            System.out.println("我是中介,我在带领租客看房子......");
            //调用目标对象的方法
            Object returnValue=method.invoke(target, arg2);
            //代理对象的功能
            System.out.println("我是中介,我在带领租客办理结束流程");
            return returnValue;
        }
        /** 生成代理对象 */
        public Object getProxyInstance(){
            //1.使用工具类
            Enhancer en=new Enhancer();
            //2.设置父类
            en.setSuperclass(target.getClass());
            //3.设置回调函数
            en.setCallback(this);
            //4.创建子类(代理)对象
            return en.create();
        }}
    
    1. 测试类

       @org.junit.Test
          public void test04() {
              UserService service = (UserService)new ProxyFactory2(new UserServiceImpl()).getProxyInstance();
              service.sell("卖房");
      }
      

      控制台打印:

      image-20230814141003722

3.3 JDK 动态代理和 CGLIB 动态代理对比

JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

4. 静态代理和动态代理的对比

灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!

JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

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

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

相关文章

运维监控学习笔记7

Zabbix的安装&#xff1a; 1、基础环境准备&#xff1a; 安装zabbix的yum源&#xff0c;阿里的yum源提供了zabbix3.0。 rpm -ivh http://mirrors.aliyun.com/zabbix/zabbix/3.0/rhel/7/x86_64/zabbix-release-3.0-1.el7.noarch.rpm 这个文件就是生成了一个zabbix.repo 2、安…

海量数据迁移,亚马逊云科技云数据库服务为大库治理提供新思路

1.背景 目前&#xff0c;文档型数据库由于灵活的schema和接近关系型数据库的访问特点&#xff0c;被广泛应用&#xff0c;尤其是游戏、互联网金融等行业的客户使用MongoDB构建了大量应用程序&#xff0c;比如游戏客户用来处理玩家的属性信息&#xff1b;又如股票APP用来存储与时…

ESP 系列的产品 ULP 协处理器的应用

参考文档&#xff1a; 《ESP32-S2 技术参考手册》 中 “1. 超低功耗协处理器 (ULP)” 章节《ESP32-S3 技术参考手册》 中 “2 超低功耗协处理器 (ULPFSM, ULPRISCV)” 章节《ESP32-C6 技术参考手册》 中 “3 低功耗处理器” 章节ULP 协处理器编程ULP RISC-V 协处理器编程Progr…

leetcode2024. 考试的最大困扰度(java)

考试的最大困扰度 leetcode2024. 考试的最大困扰度题目描述滑动窗口最大值 经典算法 leetcode2024. 考试的最大困扰度 难度 - 中等 原题链接 - 考试的最大困扰度 题目描述 一位老师正在出一场由 n 道判断题构成的考试&#xff0c;每道题的答案为 true &#xff08;用 ‘T’ 表示…

程序设计 堆

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

代码随想录算法训练营第三十四天 | 1005.K次取反后最大化的数组和,134. 加油站 ,135. 分发糖果

代码随想录算法训练营第三十四天 | 1005.K次取反后最大化的数组和&#xff0c;134. 加油站 &#xff0c;135. 分发糖果 1005.K次取反后最大化的数组和:eyes:题目总结:eyes: 134. 加油站暴力方法贪心算法&#xff08;方法一&#xff09;贪心算法&#xff08;方法二&#xff09;:…

Stable Diffusion+Temporal-kit 半虚半实应用

1.先下载temporal-kit,重启webui 2.下载好ffmpeg,配置好环境,下载Ebsynth 3.准备好你需要的视频,拖到预处理视频位置 4.填写参数,点解保存设置,然后并点击生成,会生成到目标文件夹的input位置 5.然后拉出input文件夹里面你想切换成处理的帧图片,然后填写prompt查看效…

分布式 - 服务器Nginx:一小时入门系列之HTTP反向代理

文章目录 1. 正向代理和反向代理2. 配置代理服务3. proxy_pass 命令解析4. 设置代理请求headers 1. 正向代理和反向代理 正向代理是客户端通过代理服务器访问互联网资源的方式。在这种情况下&#xff0c;客户端向代理服务器发送请求&#xff0c;代理服务器再向互联网上的服务器…

将会计转移到 cloud:远程访问软件的作用

在不断变化的金融和技术格局中&#xff0c;会计行业经历了重大演变。账簿和人工计算在金融界占据主导地位的日子已经一去不复返了。如今&#xff0c;随着企业寻求更高效、更准确、更具协作性的财务管理方式&#xff0c;数字化转变比以往任何时候都更加明显。我们正处于会计复兴…

C# Linq源码分析之Take (一)

概要 在.Net 6 中引入的Take的另一个重载方法&#xff0c;一个基于Range的重载方法。因为该方法中涉及了很多新的概念&#xff0c;所以在分析源码之前&#xff0c;先将这些概念搞清楚。 Take方法基本介绍 public static System.Collections.Generic.IEnumerable Take (this …

jeecgboot table 单元格横向和纵向合并

customRender: (value, row, index) > {const obj {children: &#xffe5;200200火力值,attrs: {}}if (index 0) {// obj.attrs.rowSpan this.dataSource.length // 合并数量 纵向合并obj.attrs.colSpan 3 // 横向合并}if (index > 1) {// obj.attrs.rowSpan 0 // …

2023上半年京东手机行业品牌销售排行榜(京东数据平台)

后疫情时代&#xff0c;不少行业都迎来消费复苏&#xff0c;我国智能手机市场在今年上半年也实现温和的复苏&#xff0c;手机市场的出货量回暖。 根据鲸参谋平台的数据显示&#xff0c;2023年上半年&#xff0c;京东平台上手机的销量为2830万&#xff0c;环比增长约4%&#xf…

注解@ConfigurationProperties获取配置文件内容 (demo)

1. 自定义配置文件内容 2. ConfigurationProperties import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;Data Component ConfigurationProperties(prefix"wx-pay.v3&qu…

从密码重置打到Getshell和其它漏洞打包

前言&#xff1a; 前几天是准备上点edusrc的分的所以就准备用手上还没刷的Nday继续上分&#xff0c;然后就有了今天这个案例&#xff1a; 信息收集&#xff1a; 之前在挖某体育学院证书的时候就挖到过一个通过修改html文件更改密码修改步骤的漏洞 所以就准备测绘一下这个资产…

dataset中图像处理提速小技巧

图像增广中&#xff0c;边缘填充颜色写元组还是写单一数值 一&#xff0c;写元组&#xff1a; self.border_fill_value 114, 114, 114 # 定义为元组# 仿射变换的时候的写法&#xff1a;merge_mosaic_image cv2.warpAffine(merge_mosaic_image, M, (self.image_size, self.…

【uniapp】微信小程序,取视频第一帧,前提是 图片是在 阿里云的oss上

上传视频等&#xff0c;默认为黑色&#xff0c;无法用视频的第一帧作为封面&#xff0c;以及视频的video为原生组件&#xff0c;层级很高无法覆盖问题&#xff0c;虽然有cover-view&#xff0c;但很多场景还是不灵活 实现的前提条件是 图片是在 阿里云的oss上 自己服务器是…

SpringCloud中 Sentinel 限流的使用

引入依赖 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>手动编写限流规则&#xff0c;缺点是不够灵活&#xff0c;如果需要改变限流规则需要修改源码…

【GitOps系列】如何解决 GitOps 的秘钥存储问题?

文章目录 简介GitOps 密钥管理方案安装 Sealed-Secrets安装 CLI 工具安装 Controller 控制器 示例应用介绍创建 ArgoCD 应用创建加密后的 Secret 对象创建 Image Pull Secret 对象创建 Secret 对象推送到代码仓库验证 Secret 原理解析核心关注点 简介 在实际的业务场景下&…

1.SpringMVC接收请求参数及数据回显:前端url地址栏传递参数通过转发显示在网页

1、SpringMVC 处理前端提交的数据 1.1 提交的域名和处理方法的参数不一致&#xff0c;使用注解解决 1.2 提交的域名和处理方法的参数不一致&#xff0c;使用注解解决 1.3 提交的是一个对象 2、前端url地址栏传递的是一个参数 请求地址url&#xff1a;http://localhost:8080/s…

openGauss学习笔记-39 openGauss 高级数据管理-分区表

文章目录 openGauss学习笔记-39 openGauss 高级数据管理-分区表39.1 范围分区表的分类39.2 创建范围分区39.2.1 创建VALUES LESS THAN范围分区表语法格式39.2.2 创建VALUES LESS THAN范围分区表参数说明39.2.3 创建VALUES LESS THAN范围分区表示例 39.3 询分区表39.3.1 查询分区…