静态代理,JDK动态代理,Cglib动态代理的写法

news2024/11/26 16:24:37

目录

    • 静态代理
    • JDK动态代理
    • Cglib动态代理
    • 小结

使用代理模式可以在不改变被代理类的情况下,实现对被代理类的进行增强。
例如:在被代理类中有一个div()方法,方法中只是计算除法,然而想要对其进行增强,如添加异常捕获等。

class CalculateImp {
    public void div(int x, int y) {
        System.out.println("x/y="+(x/y));
    }
}

静态代理

通常静态代理类与被代理类要实现统一的业务接口。

public class Application {
    public static void main(String[] args) throws InterruptedException {
        CalculateImp calculateImp = new CalculateImp();
        AgencyCalculate calculate = new AgencyCalculate(calculateImp); 
        calculate.div(1,0);
    }
}
class CalculateImp implements Calculate{

    @Override
    public void div(int x, int y) {
        System.out.println("x/y="+(x/y));
    }
}
interface Calculate{
    void div(int x,int y);
}
class AgencyCalculate implements Calculate{

    private Calculate calculate;

    public AgencyCalculate(Calculate calculate) {
        this.calculate=calculate;
    }

    @Override
    public void div(int x, int y) {
        try{
            this.before();
            calculate.div(x,y);
            this.after();
        }
        catch (Exception e){
            System.out.println("出现错误");
        }
    }
    public void before(){
        System.out.println("前置增强");
    }
    public void after(){
        System.out.println("后置增强");
    }
}

效果:
在这里插入图片描述

这种方式是在静态代理类中虽然传递的是接口类型的对象,只要实现了此接口的对象都可以作为被代理类对象。但没有实现该接口的对象却不能通过这个代理类进行代理。为此还要再为其写一个静态代理类。那如果有多个互不相关的类需要代理增强呢?而使用动态代理类就可以以为每个类自动的生成一个代理类并加载到内存中直接使用,这样使得我们不用去自己编写代理类,只需要清楚被代理的对象是谁,进行哪些增强逻辑即可。

JDK动态代理

动态代理最为核心的就是两部分:
1、哪个类需要被代理?
使用Proxy的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)去指定具体哪个类。这个方法中有三个参数:

  • ClassLoader :被代理类的加载器,通过类的加载器可以将将生成的代理类加载到内存。类的加载器可以通过被代理类的反射获取。
  • Class<?>[] interfaces:被代理类的所有实现接口,可以使得生成的代理对象也实现了这些接口。所有实现接口可以通过被代理类的反射获取。
  • InvocationHandler:这是一个接口,需要传递它的实现类。类中重写了invoke()方法,这个方法就是具体的增强逻辑。这个接口的实现就是下面要考虑的第二个问题

2、具体的增强逻辑?
通过实现InvocationHandler接口,然后重写invoke(Object proxy, Method method, Object[] args)方法。只要生成的代理类对象调用方法就会被这个invoke()方法拦截,在这个方法中进行逻辑增强,然后调用被代理类的方法。这个方法中与三个参数:

  • proxy:自动的生成的代理对象
  • method:被代理的方法
  • args:向被代理类方法中传递的参数数组

然后将生成的实现InvocationHandler接口的对象传递到上面newProxyInstance()方法第三个实例中。

注意:这种通过JDK获取动态代理对象的方式必须要实现接口。
下面我将使用JDK动态代理的方式代理上面的Calculate类,实现相同的效果。

public class Application {
    public static void main(String[] args) throws InterruptedException {
       // 被代理对象
        CalculateImp calculateImp = new CalculateImp();
	
        ProxyFactory proxyFactory = new ProxyFactory();
       
       // 通过这个类生成代理对象
        proxyFactory.setObject(calculateImp);
		// 返回的代理类类型不确定,但可以代理类一定也实现了Calculate接口,利用多态引用
        Calculate c1 = (Calculate) proxyFactory.getProxy();
		// 调用方法,这是就会被生成的代理类对象的invoke方法拦截,然后实现增强。思考:上面为什么不使用Object或其他类接收
        c1.div(1,1);
    }
}

class CalculateImp implements Calculate{
    @Override
    public void div(int x, int y) {
        System.out.println("x/y="+(x/y));
    }
}

// 要想通过JDK这种方式实现动态代理,就必须有接口
interface Calculate{
    void div(int x,int y);
}
class ProxyFactory{
    // 通过set的方式获取被代理对象 ,使用Object可以生成任意对象代理
    private Object object;

    public void setObject(Object object) {
        this.object = object;
    }
    
    // 抽象出;两个增强的方法
    private void before(){
        System.out.println("前置增强");
    }
    private void after(){
        System.out.println("后置增强");
    }
    Object getProxy(){
        InvocationHandler invocationHandler = new InvocationHandler() {  // 使用匿名内部类的方式直接实现接口,也可以使用外部类单独实现
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                before();
                try{
                    // 调用被代理对象的方法 ,代理对象调用哪个方法就会被invoke拦截,然后在这里调用被代理类的该方法
                    method.invoke(object,args);
                }
                catch (Exception e){
                    System.out.println("出现异常");
                }
                after();
               return null;  // 若代理类不为 void ,可以在这里返回最终的结果
            }
        };
        // 被代理类的类加载器,被代理类实现的接口,代理逻辑
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), 	
        							  object.getClass().getInterfaces(),
        							  invocationHandler);
    }

}

使用动态代理类的好处就是它不用单独为某个类编写代理类,使用一个动态类就可以实现任意类的代理。若代理逻辑不同,可以将代码中ProxyFactory类的InvocationHandler接口对象作为属性暴露出来。

上面是演示使用动态代理类可以达到静态代理类相同的效果,下面将演示动态代理类可以自动生成不同的类的代理对象。例如:新增加一个House类,与Calculate类有相同的代理逻辑。只需要在上面的代理基础上增加:
在这里插入图片描述

效果:
在这里插入图片描述
使用JDK动态代理是运行时自动生成代理类,此代理类是在内存中真实存在的,但名字不固定,实现了被代理类所有的接口,然后JDK底层生成后,就会通过制定的类加载器自动的放入内存中。在使用时,只需要从内存中获取即可,虽然不知道类名,但此代理类实现了被代理类的所有接口,利用多态引用的方式即可。

思考:动态生成的代理类类名是什么?
生成的代理类对象通过反射获取类的全类名:代理类对象.getClass().getName() ,最终的格式是包名.$Proxy序号,如:
在这里插入图片描述

Cglib动态代理

Cglib不是JDK自带的代理方式,要想使用它需要先引入依赖

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

使用Cglib动态代理的方式,是使用继承的方式,因此,被代理类可不必实现接口,但被代理类一定不能设置为final。因为一旦加上Final底层就不能够继承被代理类,导致代理失败。

使用Cblib还是要考虑两个问题:
1、哪个类被代理
需要创建一个Enhancer对象,然后在调用setSuperclass(Class superclass)方法。方法的参数就是被代理类

2、具体的增强逻辑?
需要实现MethodInterceptor接口,重写intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)方法,该方法有4个参数

  • Object o:生成的代理类对象,代理类是自动生成且类名不固定
  • Method method:被代理类的方法
  • Object[] objects:方法的参数列表
    以上三个分别对应着JDK上InvokerHander实现类的invoke()方法的三个参数
  • MethodProxy methodProxy:底层进行动态创建代理类的对象

下面我将使用Cglib动态代理的方式代理上面的Calculate类,实现相同的效果。

public class Application {
    public static void main(String[] args) throws InterruptedException {
        //被代理类的对象
        CalculateImp calculateImp = new CalculateImp();
        // 生成代理类的对象
        ProxyFactory proxyFactory = new ProxyFactory();

        proxyFactory.setObject(calculateImp);

        // 同样生成的类不固定名,但这个类继承了被代理类,所以利用多态即可引用即可
        CalculateImp proxy = (CalculateImp) proxyFactory.getProxy();

        // 代理类调用此方法,会转到enhancer指定的回调对象的invoke方法,这个方法就是写代理逻辑的方法
        proxy.div(1,2);
    }
}
// 注意 一定不能为final,否则生成的代理类无法继承。至于接口,与Cglib动态代理无关,这里特意去掉接口
class CalculateImp {
 
    public void div(int x, int y) {
        System.out.println("x/y="+(x/y));
    }
}

// 要想通过JDK这种方式实现动态代理,就必须有接口
interface Calculate{
   public void div(int x,int y);
}

class ProxyFactory implements MethodInterceptor {
    private Object object;

    public void setObject(Object object) {
        this.object = object;
    }

    // 抽象出;两个增强的方法
    private void before(){
        System.out.println("前置增强");
    }
    private void after(){
        System.out.println("后置增强");
    }

    // 具体增强的逻辑
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        this.before();
        // 通过反射调用代理类的方法
        method.invoke(this.object,objects);
        System.out.println(methodProxy.getClass().getName());
        this.after();
        return null;
    }
    public Object getProxy(){

        //Enhancer :增强者
        Enhancer enhancer = new Enhancer();
        // 设置要代理哪个类,因为生成的代理类是继承被代理类,所以这个接口参数名为superclass
        enhancer.setSuperclass(CalculateImp.class);

        //设置回调 就是实现了MethodInterceptor接口的实现类对象,由于这里将enhancer写在了这类内部,所以this就可
        enhancer.setCallback(this);
        
        //最后返回生成的代理类对象
        return enhancer.create();
    }
}

思考:Cglib动态生成的代理类类名是什么?
生成的代理类对象通过反射获取类的全类名:代理类对象.getClass().getName() ,最终的格式是被代理类的全类名$$EnhancerByCGLIB$$编号,如:
在这里插入图片描述

小结

使用总结
例如:对类中的方法进行增强代理,在代理类中补充方法的执行时间。

使用JDK动态代理的方式

public class Application {
    public static void main(String[] args) throws InterruptedException {
       	
        ProxyFactory proxyFactory = new ProxyFactory(被代理对象);
      
       	业务接口 proxy = (业务接口) proxyFactory.getProxy();
        proxy.业务方法;
    }
}
class ProxyFactory{
    private Object object;

    public ProxyFactory(Object object) {
        this.object = object;
    }

    private InvocationHandler getInvocationHandler(){
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long before = System.currentTimeMillis();
                method.invoke(object,args);
                long after = System.currentTimeMillis();
                System.out.println("方法执行完毕。共耗时"+(after-before)+"毫秒");
                return null;
            }
        };
        return invocationHandler;
    }
    public Object getProxy() {
       return Proxy.newProxyInstance(object.getClass().getClassLoader(), 
       								 object.getClass().getInterfaces(),
       								 getInvocationHandler());
    }
}

利用Cglib动态代理方式

public class Application {
    public static void main(String[] args) throws InterruptedException {
        
        ProxyFactory proxyFactory = new ProxyFactory(被代理对象);
        被代理类 proxy = (被代理类) proxyFactory.getProxy();
      	proxy.业务方法;
    }
}
class ProxyFactory implements MethodInterceptor {
    private Object object;

    public ProxyFactory(Object object) {
        this.object = object;
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long before = System.currentTimeMillis();
        method.invoke(object,objects);
        long after = System.currentTimeMillis();
        System.out.println("方法执行完毕。共耗时"+(after-before)+"毫秒");
        return null;
    }
    public Object getProxy(){

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Test.class);
        enhancer.setCallback(this);
        return enhancer.create();
    }
}

Cglib动态代理相较于JDK动态代理的优点

  • Cglib的性能更高
  • Cglib无需被代理类实现接口,代码入侵更少

Cglib动态代理相较于JDK动态代理的缺点

  • Cglib动态需要引依赖,而JDK动态代理原生支持,可随着JDK版本的升级进行平滑升级。而Cglib可能还要引入新版本Jar包
  • 被代理类不能被final修饰

JDK动态代理利用的Java反射机制,被代理类必须实现业务接口;Cglib是基于ASM机制实现,被代理类不能被final修饰。使用动态代理可以自动为被代理类生成代理类并将代理类对象加载到内存。

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

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

相关文章

Element UI框架学习篇(一)

Element UI框架学习篇(一) 1.准备工作 1.1 下载好ElementUI所需要的文件 ElementUI官网 1.2 插件的安装 1.2.1 更改标签的时实现自动修改 1.2.2 element UI提示插件 1.3 使用ElementUI需要引入的文件 <link rel"stylesheet" href"../elementUI/element…

【JavaScript】理解面向对象以及构造函数的推导

&#x1f4bb; 【JavaScript】理解面向对象以及构造函数的推导 &#x1f3e0;专栏&#xff1a;JavaScript &#x1f440;个人主页&#xff1a;繁星学编程&#x1f341; &#x1f9d1;个人简介&#xff1a;一个不断提高自我的平凡人&#x1f680; &#x1f50a;分享方向&#xf…

面试题:Redis网络模型

1 用户空间和内核空间以Centos 7 linux操作系统为例。计算机系统被内核操控&#xff0c; 内核被应用操控。为了避免用户应用导致冲突甚至内核崩溃&#xff0c;用户应用与内核是分离的进程的寻址空间会划分为两部分:内核空间、用户空间。用户空间只能执行受限的命令(Rin3&#x…

解决Mac 安装应用提示:xx已损坏,无法打开。 您应该将它移到废纸篓问题

许多新手mac 用户安装应用得时候会出现 “已损坏&#xff0c;无法打开。您应该将它移到废纸娄” 导致无法正常安装&#xff0c;其实应用软件b并没有损坏&#xff0c;只是系统安全设置&#xff0c;我们改如何解决呢&#xff1f; 1、开启允许任何来源 苹果已经取消了允许“任何…

以数据驱动管理场景,低代码助力转型下一站

数据驱动 数据驱动&#xff0c;是通过移动互联网或者其他的相关软件为手段采集海量的数据&#xff0c;将数据进行组织形成信息&#xff0c;之后对相关的信息讲行整合和提炼&#xff0c;在数据的基础上经过训练和拟合形成自动化的决策模型&#xff0c;简单来说&#xff0c;就是…

Python项目实战——外汇牌价(附源码)

前言 几乎每个人都在使用银行卡&#xff0c;今天我们就来爬取某行外汇牌价&#xff0c;获取我们想要的数据。 环境使用 python 3.9pycharm 模块使用 requests 模块介绍 requestsrequests是一个很实用的Python HTTP客户端库&#xff0c;爬虫和测试服务器响应数据时经常会用到&…

5分钟搞懂 强缓存与协商缓存

Ⅰ、http缓存 HTTP 缓存策略 分为 > 「强制缓存」 和 「协商缓存」 为什么需要 HTTP 缓存 呢 ? &#x1f447; 直接使用缓存速度 >> 远比重新请求快 缓存对象有那些呢 &#xff1f;&#x1f447; 「图片」 「JS文件」 「CSS文件」 等等 文章目录Ⅰ、http缓存Ⅱ…

震惊!邻桌的程序猿做可视化报告竟然比我还快,带着好奇心我打开了他的电脑,发现惊天秘密,原因竟是...

其实&#xff0c;本文就是想分享一个做可视化的捷径&#xff01; 制作可视化的方式有千千万。 Excel 控若能轻车熟路驾驭 VBA&#xff0c;能玩出各种花来&#xff0c;再不济借助图表插件外援也能秒杀一众小白选 手。 会编程的&#xff0c;Echarts 几十行代码&#xff0c;分分…

Flink反压如何排查

Flink反压利用了网络传输和动态限流。Flink的任务的组成由流和算子组成&#xff0c;那么流中的数据在算子之间转换的时候&#xff0c;会放入分布式的阻塞队列中。当消费者的阻塞队列满的时候&#xff0c;则会降低生产者的处理速度。 如上图所示&#xff0c;当Task C 的数据处…

nuxt 学习笔记

这里写目录标题路由跳转NuxtLinkquery参数params参数嵌套路由tab切换效果layouts 文件夹强制约定放置所有布局文件&#xff0c;并以插槽的形式作用在页面中1.在app.vue里面2.component 组件使用Vue < component :is"">Vuex生命周期数据请求useFetchuseAsyncDat…

鸿蒙设备学习|快速上手BearPi-HM Micro开发板

系列文章目录 第一章 鸿蒙设备学习|初识BearPi-HM Micro开发板 第二章 鸿蒙设备学习|快速上手BearPi-HM Micro开发板 文章目录系列文章目录前言一、环境要求1.硬件要求2.软件要求3.Linux构建工具要求4.Windows开发工具要求5.工具下载地址二、安装编译基础环境1.安装Linux编译环…

【速通版】吴恩达机器学习笔记Part1

准备速通一下吴恩达的机器学习 很快做个笔记5.2.3 监督学习 part 2_哔哩哔哩_bilibili 1.概述&#xff08;P1-P3) ML是AI的重要部分&#xff0c;AI的目标是AGI&#xff08;artificial general intelligence&#xff09;但是目前就。。。。 supervised learning&#xff1a;目…

如何开发L2毫秒接口?

L2毫秒接口普遍应用于大众的日常生活中&#xff0c;并且很多的企业通过api进行数据内容的调用&#xff0c;从而在技术上和成本上得到福利。 进行数据的整合与共享是L2毫秒接口的主要用途之一&#xff0c;所以开发L2毫秒接口就必须慎重&#xff0c;注意安全隐患&#xff0c;防止…

VHDL语言基础-组合逻辑电路-其它组合逻辑模块

目录 多路选择器&#xff1a; 逻辑功能&#xff1a; 常用的类型&#xff1a; 4选1多路选择器的实现&#xff1a; 求补器&#xff1a; 求补器的实现&#xff1a; 三态门&#xff1a; 三态门的应用实例&#xff1a; 三态门的实现&#xff1a; 缓冲器&#xff1a; 什么是…

Lesson 6.4 逻辑回归手动调参实验

文章目录一、数据准备与评估器构造1. 数据准备2. 构建机器学习流二、评估器训练与过拟合实验三、评估器的手动调参在补充了一系列关于正则化的基础理论以及 sklearn 中逻辑回归评估器的参数解释之后&#xff0c;接下来&#xff0c;我们尝试借助 sklearn 中的逻辑回归评估器&…

Ts笔记第一天

文章目录安装 ts运行环境 nodeTS类型数字 、字符串 和布尔类型字面量any 和unknown类型断言void和neverobjectArraytuple 元组enum 枚举安装 ts运行环境 node node-v看版本号 2. 安装ts -g全局安装 npm i -g typescript // 这里全局安装 -s安装无法使用tsc 创建一个01.ts文…

第五十章 动态规划——数位DP模型

第五十章 动态规划——数位DP模型一、什么是数位DP数位DP的识别数位DP的思路二、例题1、AcWing 1083. Windy数&#xff08;数位DP&#xff09;2、AcWing 1082. 数字游戏&#xff08;数位DP&#xff09;3、AcWing 1081. 度的数量&#xff08;数位DP&#xff09;一、什么是数位DP…

供应PEG试剂AC-PEG-COOH,Acrylate-PEG-Acid,丙烯酸酯-PEG-羧基

英文名称&#xff1a;AC-PEG-COOH&#xff0c;Acrylate-PEG-Acid 中文名称&#xff1a;丙烯酸酯-聚乙二醇-羧基 丙烯酸酯-PEG-COOH是一种含有丙烯酸酯和羧酸的线性杂双功能PEG试剂。它是一种有用的带有PEG间隔基的交联剂。丙烯酸酯可与紫外光或自由基引发剂聚合。丙烯酸酯-PE…

从FPGA说起的深度学习(二)

这是新的系列教程&#xff0c;在本教程中&#xff0c;我们将介绍使用 FPGA 实现深度学习的技术&#xff0c;深度学习是近年来人工智能领域的热门话题。在本教程中&#xff0c;旨在加深对深度学习和 FPGA 的理解。用 C/C 编写深度学习推理代码高级综合 (HLS) 将 C/C 代码转换为硬…

Leetcode.1797 设计一个验证系统

题目链接 Leetcode.1797 设计一个验证系统 Rating : 1534 题目描述 你需要设计一个包含验证码的验证系统。每一次验证中&#xff0c;用户会收到一个新的验证码&#xff0c;这个验证码在 currentTime时刻之后 timeToLive秒过期。如果验证码被更新了&#xff0c;那么它会在 curr…