代理模式--静态代理和动态代理

1.代理模式

定义:代理模式就是代替对象具备真实对象的功能,并代替真实对象完成相应的操作并且在不改变真实对象源代码的情况下扩展其功能,在某些情况下,⼀个对象不适合或者不能直接引⽤另⼀个对象,⽽代理对象可以在客户端和⽬标对象之间起到中介的作⽤

使用代理模式可以降低系统的耦合性,扩展性好,并且可以起到保护目标对象的作用
例如:我们平时租房的过程,租房中介就相当于代理类
代理模式分为静态代理和动态代理

2.静态代理

静态代理实现步骤:

  1. 定义⼀个接⼝及其实现类(目标类);
  2. 创建⼀个代理类同样实现这个接⼝(继承同一个接口的原因就是,代理类需要拥有和目标类同样的方法这样才能代理)
  3. 将⽬标对象注⼊进代理类,然后在代理类的对应⽅法调⽤⽬标类中的对应⽅法。
public interface IRentHouse {
    void rent();
}

public class RentHouse implements IRentHouse{
    @Override
    public void rent() {
        System.out.println("租户租房子");
    }
}

public class IntermediaryProxy implements IRentHouse{
    private IRentHouse iRentHouse;
    public IntermediaryProxy(IRentHouse iRentHouse) {
        this.iRentHouse = iRentHouse;
    }
    @Override
    public void rent() {
        System.out.println("交中介费");
        iRentHouse.rent();
        System.out.println("租房子后中介负责维护管理");

    }
}
/**
 * 测试类
 */
public class Test {
    public static void main(String[] args) {
        // 定义租房
        IRentHouse iRentHouse = new RentHouse();
        // 定义中介
        IRentHouse proxy = new IntermediaryProxy(iRentHouse);
        // 租房
        proxy.rent();
    }
}

运行结果如下:
在这里插入图片描述
静态代理有很多缺点,实际应用场景非常少,几乎不用
对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口中一旦新增方法,目标对象和代理对象都要修改),且麻烦(需要对每个目标类都单独写一个代理类)

3.动态代理

相比于静态代理来说,动态代理更加灵活,不需要针对每个目标类都单独创建一个代理类,也不需要我们必须实现接口
动态代理允许使用一种方法的单个类(代理类),为具有任意数量方法的任意类(目标类)的多个方法提供服务,看到这句话,是不是联想到动态代理的实现与Java反射机制密不可分

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个属性和方法,这种动态获取的信息以及动态调用对象的方法的功能称之为Java语言的反射机制

从 JVM ⻆度来说,动态代理是在运⾏时动态⽣成类字节码,并加载到 JVM 中
的。说到动态代理,不得不提的是Spring AOP,它的实现依赖了动态代理

代理类的两个作用
1.添加增强方法,2.调用目标类

1.jdk动态代理(接口代理)

在 Java 动态代理机制中java.long.reflect包中的 InvocationHandler 接⼝和 Proxy 类是核⼼

实际上就是在内存中生产一个对象,该对象实现了指定的目标对象的所有接口,代理对象和目标对象是兄弟关系,
jdk自带动态代理技术,需要使用一个静态方法来创建代理对象,他需要目标对象必须实现接口,生产的代理对象和目标对象都实现同一个接口

JDK 动态代理类使⽤步骤:

  1. 定义⼀个接⼝及其实现类;
  2. ⾃定义 InvocationHandler 并重写invoke⽅法,在 invoke ⽅法中我们会调⽤ 原⽣⽅法(被代理类的⽅法)并⾃定义⼀些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) ⽅法创建代理对象;

1.定义JDK动态代理类

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

// JDK 动态代理类
public class JDKInvocationHandler implements InvocationHandler {

    //⽬标对象即就是被代理对象
    private Object target;

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

    /**
     *
     * @param proxy 代理对象
     * @param method 代理方法
     * @param args 参数
     *
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1234就是一些增强方法
        //1.安全检查
        System.out.println("安全检查");
        //2.记录⽇志
        System.out.println("记录⽇志");
        //3.时间统计开始
        System.out.println("记录开始时间");
        //通过反射调⽤被代理类的⽅法
        Object retVal = method.invoke(target, args);
        //4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }
}

2.创建⼀个代理对象并使用

public class Main {
    public static void main(String[] args) {
        // 代理对象
        PayService target= new AliPayService();
        // 静态的是已经写好了的
        // 动态的创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
        PayService proxy = (PayService) Proxy.newProxyInstance(
                // 通过目标类的getClassLoader
                target.getClass().getClassLoader(),
                // 被代理类实现的一些接口
                new Class[]{PayService.class},
                // 实现了InvocationHandler接口的对象
                new JDKInvocationHandler(target)
        );
        proxy.pay();
    }

}

Proxy 类中使⽤频率最⾼的⽅法是:newProxyInstance() ,这个⽅法主要⽤来⽣成⼀个代理对象

public static Object newProxyInstance(ClassLoader loader,
									Class<?>[] interfaces,
 									InvocationHandler h)
 									throws IllegalArgumentException
 {
 ....
 }

这个⽅法⼀共有 3 个参数:

  1. loader :类加载器,⽤于加载代理对象。
  2. interfaces : 被代理类实现的⼀些接⼝;
  3. h : 实现了 InvocationHandler 接⼝的对象;

运行main方法
在这里插入图片描述
JDK 动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类,为了解决这个问题,我们可以⽤ CGLIB 动态代理机制来避免

CGLIB(Code GenerationLibrary)是⼀个基于ASM的字节码⽣成库,它允许我们在运⾏时对字节码进⾏修改和动态⽣成。CGLIB通过继承⽅式实现代理。很多知名的开源框架都使⽤到了CGLIB, 例如 Spring 中的 AOP 模块中:如果⽬标对象实现了接⼝,则默认采⽤JDK 动态代理,否则采⽤ CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接⼝和 Enhancer 类是核⼼

2.CGLIB 动态代理类使⽤步骤

  1. 定义⼀个类;
  2. ⾃定义 MethodInterceptor 并重写 intercept ⽅法,intercept ⽤于拦截增强
    被代理类的⽅法,和 JDK 动态代理中的 invoke ⽅法类似;
  3. 通过 Enhancer 类的 create()创建代理类

1.添加依赖

和JDK 动态代理不同, CGLIB(Code Generation Library) 实际是属于⼀个开源项⽬,如果你要使⽤它的话,需要⼿动添加相关依赖

<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.3.0</version>
</dependency>

2.⾃定义 MethodInterceptor(⽅法拦截器)

public class CGLIBInterceptor implements MethodInterceptor {
 //被代理对象
 private Object target;
 public CGLIBInterceptor(Object target){
 this.target = target;
 }
 @Override
 public Object intercept(Object o, Method method, Object[] args, Method
Proxy methodProxy) throws Throwable {
 //1.安全检查
 System.out.println("安全检查");
 //2.记录⽇志
 System.out.println("记录⽇志");
 //3.时间统计开始
 System.out.println("记录开始时间");
 //通过cglib的代理⽅法调⽤
 Object retVal = methodProxy.invoke(target, args);
 //4.时间统计结束
 System.out.println("记录结束时间");
 return retVal;
 }
}

3.创建代理类, 并使⽤

public static void main(String[] args) {
 PayService target= new AliPayService();
 PayService proxy= (PayService) Enhancer.create(target.getClass(),ne
w CGLIBInterceptor(target));
 proxy.pay();
 }

你需要⾃定义 MethodInterceptor 并重写 intercept ⽅法,intercept ⽤于拦截增强被代理类的⽅法

public interface MethodInterceptor
extends Callback{
 // 拦截被代理类中的⽅法
 public Object intercept(Object obj, java.lang.reflect.Method method, Ob
ject[] args,MethodProxy proxy) throws Throwable;
}
  • obj : 被代理的对象(需要增强的对象)
  • method : 被拦截的⽅法(需要增强的⽅法)
  • args : ⽅法⼊参
  • proxy : ⽤于调⽤原始⽅法

3.JDK 动态代理和 CGLIB 动态代理对⽐:

  • JDK 动态代理只能代理实现了接⼝的类或者直接代理接⼝,⽽ CGLIB 可以代理未实现任何接⼝的类
  • CGLIB动态代理是通过⽣成⼀个被代理类的⼦类来拦截被代理类的⽅法调⽤,因此不能代理声明为 final

性能: ⼤部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显

Spring代理选择

  1. proxyTargetClass 为false, ⽬标实现了接⼝, ⽤jdk代理
  2. proxyTargetClass 为false, ⽬标未实现接⼝, ⽤cglib代理
  3. proxyTargetClass 为true, ⽤cglib代理

下篇见~
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CentOS 8 错误: Error setting up base repository

配置ip、掩码、网关、DNS VMware网关可通过如下查看 打开网络连接 配置镜像的地址 vault.centos.org/8.5.2111/BaseOS/x86_64/os/

python 面向对象编程的特点 - 封装 - 继承(经典类、新式类) - 多态 - 静态方法、类方法 - 下划线的使用 - 回合制攻击游戏实验

目录 面向对象编程的特点&#xff1a; 封装&#xff1a;封装是将数据和操作&#xff08;方法&#xff09;封装在一个对象中的能力 继承&#xff1a;继承是指一个类&#xff08;子类&#xff09;可以继承另一个类&#xff08;父类&#xff09;的属性和方法。 我们为什么需要继…

PostgreSQL构建时间

– PostgreSQL构建时间 select make_timestamp(2023,7,27,7,34,16);

Ubuntu—vi编辑器的使用一

vi编辑器 vi是Linux中最基本的编辑器。但vi编辑器在系统管理、服务器配置工作中永远都是无可替代的。 vi编辑器的使用 vi有以下三种模式 命令行模式 用户在用vi编辑文件时&#xff0c; 最初进入的是该模式。可以进行复制、粘贴等操作 插入模式 进行文件编辑&#xff0c; 按…

Java的第十五篇文章——网络编程(后期再学一遍)

目录 学习目的 1. 对象的序列化 1.1 ObjectOutputStream 对象的序列化 1.2 ObjectInputStream 对象的反序列化 2. 软件结构 2.1 网络通信协议 2.1.1 TCP/IP协议参考模型 2.1.2 TCP与UDP协议 2.2 网络编程三要素 2.3 端口号 3. InetAddress类 4. Socket 5. TCP网络…

VMPWN的入门系列-2

温馨提示&#xff1a; 文章有点长&#xff0c;图片比较多&#xff0c;请耐心阅读 实验四 VMPWN4 题目简介 这道题应该算是虚拟机保护的一个变种&#xff0c;是一个解释器类型的程序&#xff0c;何为解释器&#xff1f;解释器是一种计算机程序&#xff0c;用于解释和执行源代码。…

PKG内容查看工具:Suspicious Package for Mac安装教程

Suspicious Package Mac版是一款Mac平台上的查看 PKG 程序包内信息的应用&#xff0c;Suspicious Package Mac版支持查看全部包内全部文件&#xff0c;比如需要运行的脚本&#xff0c;开发者&#xff0c;来源等等。 suspicious package mac使用简单&#xff0c;只需在选择pkg安…

螺旋矩阵 II

给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]] 示例 2&#xff1a; 输入&#xff1a;n 1 输出&a…

软工导论知识框架(二)结构化的需求分析

本章节涉及很多重要图表的制作&#xff0c;如ER图、数据流图、状态转换图、数据字典的书写等&#xff0c;对初学者来说比较生僻&#xff0c;本贴只介绍基础的轮廓&#xff0c;后面会有单独的帖子详解各图表如何绘制。 一.结构化的软件开发方法&#xff1a;结构化的分析、设计、…

【并发编程】ForkJoinPool工作原理分析

目录 前置内容课程内容一、由一道算法题引发的思考1.算法题2.什么是归并排序法 二、什么是Fork/Join框架1.基本介绍2.ForkJoinPool2.ForkJoinPool构造函数及参数解读3.任务提交方式4.工作原理图5.工作窃取6.和普通线程池之间的区别7.ForkJoinTask 学习总结 前置内容 Q1&#x…

WEB:web2

背景知识 代码审计 题目 由上述可知&#xff0c;这段代码定义了一个函数encode&#xff0c;接受一个字符串参数$str&#xff0c;并返回对其进行加密后的结果 加密算法包括&#xff1a; 使用strrev函数将字符串进行翻转&#xff1b;对翻转后的每个字符&#xff0c;将其ASCII值…

helm部署rabbitmq

1.添加rabbitmq仓库并下载包 helm repo add bitnami https://charts.bitnami.com/bitnami helm pull bitnami/rabbitmq --version 10.1.4 tar -zxvf rabbitmq-10.1.4.tgz mv values.yaml values.yaml.back grep -v "#" values.yaml.back > values.yaml2.helm部署…

xxl-Job分布式任务调度

1.概述 1.1 什么是任务调度 我们可以先思考一下业务场景的解决方案&#xff1a; 某电商系统需要在每天上午10点&#xff0c;下午3点&#xff0c;晚上8点发放一批优惠券。某银行系统需要在信用卡到期还款日的前三天进行短信提醒。某财务系统需要在每天凌晨0:10结算前一天的财…

系统架构设计师-软件架构设计(3)

目录 一、软件架构风格&#xff08;其它分类&#xff09; 1、闭环控制结构&#xff08;过程控制&#xff09; 2、C2风格 3、MDA&#xff08;模型驱动架构 Model Driven Architecture&#xff09; 4、特定领域软件架构&#xff08;DSSA&#xff09; 4.1 DSSA基本活动及产出物…

MySQL之深入InnoDB存储引擎——Checkpoint机制

文章目录 一、引入二、LSN三、触发时机 一、引入 由于页的操作首先都是在缓冲池中完成的&#xff0c;那么如果一条DML语句改变了页中的记录&#xff0c;那么此时页就是脏的&#xff0c;即缓冲池中页的版本要比磁盘的新。那么数据库需要将新版本的页刷新到磁盘。倘若每次一个页…

Unity源码分享-黄金矿工游戏完整版

Unity源码分享-黄金矿工游戏完整版 项目地址&#xff1a;https://download.csdn.net/download/Highning0007/88118933

Raki的读paper小记:RWKV: Reinventing RNNs for the Transformer Era

Abstract&Introduction&Related Work 研究任务 基础模型架构已有方法和相关工作 RNN&#xff0c;CNN&#xff0c;Transformer稀疏注意力&#xff08;Beltagy等人&#xff0c;2020年&#xff1b;Kitaev等人&#xff0c;2020年&#xff1b;Guo等人&#xff0c;2022年&am…

arm 函数栈回溯

大概意思就是arm每个函数开始都会将PC、LR、SP以及FP四个寄存器入栈。 下面我们看一下这四个寄存器里面保存的是什么内存 arm-linux-gnueabi-gcc unwind.c -mapcs -w -g -o unwind&#xff08;需要加上-mapcs才会严格按照上面说的入栈&#xff09; #include <stdio.h> …

Flutter 开发者工具 Android Studio 开发Flutter应用

Flutter 开发者工具 在 Android Studio 开发Flutter应用 &#x1f525; Android Studio 版本更新 &#x1f525; Android Studio Check for Update Connection failed ​ 解决方案 如果是运行的是32位的android studio需要在andriod studio的启动目录下找到studio.exe.vmoptio…

Flutter-基础Widget

Flutter页面-基础Widget 文章目录 Flutter页面-基础WidgetWidgetStateless WidgetStateful WidgetState生命周期 基础widget文本显示TextRichTextDefaultTextStyle 图片显示FlutterLogoIconImageIamge.assetImage.fileImage.networkImage.memory CircleAvatarFadeInImage 按钮R…