当前位置: 首页 > article >正文

spring-第十二章 GoF代理模式

spring


文章目录

  • spring
  • 前言
  • 1.代理模式理解
  • 2.静态代理
  • 3.动态代理
    • 3.1JDK动态代理
      • 3.1.1公共接口
      • 3.1.2目标对象
      • 3.1.3客户端
      • 3.1.4额外功能代码
    • 3.2CGLIB动态代理
  • 总结


前言

在学习AOP之前,我们得先学习一下代理模式,以帮助我们理解AOP思想。


1.代理模式理解

代理模式是AOP的实现思想。所以这里有必要先了解代理模式原理。
首先,按照字面意思,代理模式就是找个人来帮自己做事,这样做有两个原因:

  • 防止自己受伤——保护自己
  • 自己无法完成这个工作——功能增强

在代理模式中,我们有三种角色:

  • 目标对象:需要代理的对象
  • 代理对象:为目标对象执行代理功能的对象
  • 目标对象和代理对象的公共接口:目标对象和代理对象都需要实现的接口。

这里要明白,我们设置目标对象和代理对象的公共接口的目的是为了统一目标对象和代理对象间的行为。
比如,如果我的目标对象中有一个删除数据的功能,需要另一个代理对象来对该功能进行增强。那么首先要保证——目标对象和代理对象中都有这个删除数据的功能(或方法),才能让目标对象把这个任务交给代理对象去做。而这一点,可以通过设置一个两者共用的接口来完成。
以图的方式表示如下:
请添加图片描述

可以看出,由代理对象来实际对接用户完成实际任务,但对于用户而言只会觉得调用的是目标对象中的delete()功能.
而在具体的代码实现中,我们有两种方式:

  • 静态代理:手动完成代理对象的创建
  • 动态代理:由字节码生成机在程序运行时自动生成代理对象

2.静态代理

静态代理就是由我们自己手动的创建目标对象、代理对象、目标对象和代理对象公共接口。
下面以一个案例说明如何编写。
假设我们已经有了Service层实现类及其接口——OrderService、OrderServiceImpl
其代码如下:
OrderService

package com.powernode.mall.service;  
  
/**  
 * 订单接口   
 **/  
public interface OrderService {  
    /**  
     * 生成订单  
     */  
    void generate();  
  
    /**  
     * 查看订单详情  
     */  
    void detail();  
  
    /**  
     * 修改订单  
     */  
    void modify();  
}

OrederServiceImpl

package com.powernode.mall.service.impl;  
  
import com.powernode.mall.service.OrderService;  
  
public class OrderServiceImpl implements OrderService {  
    @Override  
    public void generate() {  
        try {  
            Thread.sleep(1234);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("订单已生成");  
    }  
  
    @Override  
    public void detail() {  
        try {  
            Thread.sleep(2541);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("订单信息如下:******");  
    }  
  
    @Override  
    public void modify() {  
        try {  
            Thread.sleep(1010);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("订单已修改");  
    }  
}

该service功能正常,但是现在需要对其功能进行增强——需要计算每个方法的执行时间
直接的想法可能是去修改实现类中的代码,但是这样违反了OCP原则,我们不应该为了增加新功能而去修改已经完成的正常运行的代码。

所以我们会考虑使用代理模式进行工作增强。
在这种情况下,我们可以分析出如下关系:

OrderService——目标对象和代理对象公共接口
OrderServiceImpl——目标对象

所以我们还需要手动为创建代理对象

package com.powernode.mall.service;  
  
public class OrderServiceProxy implements OrderService{ // 代理对象  
  
    // 目标对象  
    private OrderService orderService;  
  
    // 通过构造方法将目标对象传递给代理对象  
    public OrderServiceProxy(OrderService orderService) {  
        this.orderService = orderService;  
    }  
  
    @Override  
    public void generate() {  
        long begin = System.currentTimeMillis();  
        // 执行目标对象的目标方法  
        orderService.generate();  
        long end = System.currentTimeMillis();  
        System.out.println("耗时"+(end - begin)+"毫秒");  
    }  
  
    @Override  
    public void detail() {  
        long begin = System.currentTimeMillis();  
        // 执行目标对象的目标方法  
        orderService.detail();  
        long end = System.currentTimeMillis();  
        System.out.println("耗时"+(end - begin)+"毫秒");  
    }  
  
    @Override  
    public void modify() {  
        long begin = System.currentTimeMillis();  
        // 执行目标对象的目标方法  
        orderService.modify();  
        long end = System.currentTimeMillis();  
        System.out.println("耗时"+(end - begin)+"毫秒");  
    }  
}

代理对象中会引入目标对象作为属性,然后在调用目标对象前后进行额外的功能补强。
这么一来,我们在实际使用时,调用的是代理对象中的方法,就能够使用原有的功能以及新增的功能了。

3.动态代理

动态代理中我们不用自己手动创建代理对象,而是由在代码运行时动态的创建代理对象。
能够动态生成类的技术常见的有:JDK动态代理、CGLIB动态代理。

3.1JDK动态代理

由jdk反射包中的Proxy类来完成,但是只能代理接口。
下面我们使用JDK动态代理完成前面静态代理中相同的案例。
首先,我们的OrderService接口和OrderServiceImpl的代码不变:

3.1.1公共接口

OrderService

package com.powernode.mall.service;  
  
  
public interface OrderService {  
    /**  
     * 生成订单  
     */  
    void generate();  
  
    /**  
     * 查看订单详情  
     */  
    void detail();  
  
    /**  
     * 修改订单  
     */  
    void modify();  
}

3.1.2目标对象

OrderServiceImpl

package com.powernode.mall.service.impl;  
  
import com.powernode.mall.service.OrderService;  
  
  
public class OrderServiceImpl implements OrderService {  
    @Override  
    public void generate() {  
        try {  
            Thread.sleep(1234);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("订单已生成");  
    }  
  
    @Override  
    public void detail() {  
        try {  
            Thread.sleep(2541);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("订单信息如下:******");  
    }  
  
    @Override  
    public void modify() {  
        try {  
           Thread.sleep(1010);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("订单已修改");  
    }  
}

3.1.3客户端

这里我们不需要手动创建一个类作为代理对象,而是使用Proxy类来创建代理对象

public class Client {  
    public static void main(String[] args) {  
//        创建目标对象  
        OrderService orderService = new OrderServiceImpl();  
//        创建代理对象  
        OrderService o = (OrderService) Proxy.newProxyInstance(orderService.getClass().getClassLoader(),  
                orderService.getClass().getInterfaces(), new TimeInvocationHandle());  
//        调用代理方法  
        o.generate();  
    }  
}

从上面的代码可知,我们使用newProxyInstance()方法来动态的创建代理对象。
所以我们先介绍一下这个方法。
以下为源码:
请添加图片描述

发现该方法需要三个参数:

  • loader:类加载器,因为在内存中也是要生成代理对象的字节码的,所以需要使用类加载器加载。需要传入和目标对象一样的类加载器,所以使用时使用反射机制获取目标对象的类加载器作为参数即可。
  • interfaces:一开始说过,这种代理模式是为接口进行代理,所以这里的第二个参数就是我们需要代理的接口,也是我们案例中的目标对象和代理对象公共接口,使用时候也通过目标对象使用反射机制获取即可。
  • h:可以看出该参数是一个InvocationHandler接口,该接口是我们写增强代码的地方,我们需要自己创建该接口的实现类对象然后作为参数传入。

3.1.4额外功能代码

前面说过newProxyInstance()的第三个参数需要的InvocationHandler接口是我们写增强代码的地方,那么现在我们需要手动创建一个InvocationHandler的实现类,并在里面编写额外的功能代码。
创建出的实现类基本结构如下:

package org.example.proxy;  
  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
  
public class TimeInvocationHandle implements InvocationHandler {  
  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        System.out.println("invoke执行");  
        return null;  
    }  
}

其中invoke()方法就是我们的“额外功能”,该方法会在我们执行了目标对象中的方法后自动执行。
那么又有问题了,我们如何决定这些“额外功能”哪些在原方法前执行,哪些在原方法之后执行呢?
这些就需要了解一下invoke()方法的各个参数了:

  • proxy:代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
  • method:目标方法,看类型可以看出是反射机制中的Method类,所以我们可以使用该参数并利用反射机制在invoke()方法中调用“目标方法”。
  • args:是一个数组,上一个参数允许我们调用“目标方法”,而这个参数允许我们向“目标方法”中传递参数。

上面的参数中我们可以获取到调用“目标方法”所需的Method类对象、参数、但是我们还要拿到目标对象。所以我们还需要在这个InvocationHandler实现类中设置一个目标对象类型的属性,然后我们可以使用有参构造器进行属性赋值。
于是我们可以在invoke()方法中调用原方法(“目标方法”),并自由决定在其前后添加额外的功能代码,最终我们的实现类如下:

package org.example.proxy;  
  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
  
  
public class TimerInvocationHandler implements InvocationHandler {  
    // 目标对象  
    private Object target;  
  
    // 通过构造方法来传目标对象  
    public TimerInvocationHandler(Object target) {  
        this.target = target;  
    }  
  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        // 目标执行之前增强。  
        long begin = System.currentTimeMillis();  
        // 调用目标对象的目标方法  
        Object retValue = method.invoke(target, args);  
        // 目标执行之后增强。  
        long end = System.currentTimeMillis();  
        System.out.println("(end - begin)+"毫秒");  
        // 一定要记得返回哦。  
        return retValue;  
    }  
}

需要注意的是,如果原方法是有返回值的,那么我们实现类的invoke方法中也要记得设置返回值。
并且我们既然要用构造方法为属性赋值,则客户端的代码创建对象时也要把目标对象传入:

public class Client {  
    public static void main(String[] args) {  
//        创建目标对象  
        OrderService orderService = new OrderServiceImpl();  
//        创建代理对象  
        OrderService o = (OrderService) Proxy.newProxyInstance(orderService.getClass().getClassLoader(),  
                orderService.getClass().getInterfaces(), new TimeInvocationHandle(orderService));  
//        调用代理方法  
        o.generate();  
    }  
}

请添加图片描述

最后,我们使用Proxy创建出来的对象任意调用一个目标方法都会添加上额外功能的代码。
请添加图片描述

3.2CGLIB动态代理

是一个开源项目,它既可以代理接口,又可以代理类,底层是通过继承的方式实现的(所以需要代理的类不能被final修饰),底层有一个小而快的字节码处理框架ASM。


总结

我们简单学习了代理模式,这是我们学习AOP的前提。


http://www.kler.cn/a/370186.html

相关文章:

  • STM32+W5500+以太网应用开发+003_TCP服务器添加OLED(u8g2)显示状态
  • 2025年最新汽车零部件企业销售项目管理解决方案
  • 【RAG落地利器】向量数据库Chroma入门教程
  • EAMM: 通过基于音频的情感感知运动模型实现的一次性情感对话人脸合成
  • Hnu电子电路实验2
  • ConvBERT:通过基于跨度的动态卷积改进BERT
  • Android Studio安装完成后,下载gradle-7.4-bin.zip出现连接超时
  • 将 Logstash 管道转换为 OpenTelemetry Collector 管道
  • JavaScript如何判断变量数据类型 - 2024最新版前端秋招面试短期突击面试题【100道】
  • SpringBoot 集成RabbitMQ 实现钉钉日报定时发送功能
  • [LeetCode] 526. 优美的排列
  • Docker | 校园网上docker pull或者docker run失败的一种解决方法
  • 探索C嘎嘎:认识string类
  • 【大数据分析与挖掘模型】matlab实现——非线性回归预测模型
  • 【计算机网络 - 基础问题】每日 3 题(五十七)
  • 《等保测评:安全与发展的双轮驱动》
  • 14 C语言中的关键字
  • Prometheus+Telegraf实现自定义监控项配置
  • RDD的常用转换算子
  • Qt实现播放器顶部、底部悬浮工具栏
  • typescript学习计划(一)--简单介绍typescript
  • VUE组件学习 | 六、v-if, v-else-if, v-else组件
  • OpenAI否认今年将发布“Orion”模型,其语音转写工具Whisper被曝存在重大缺陷|AI日报
  • 【C++】--------- 内存管理
  • 【spark】spark structrued streaming读写kafka 使用kerberos认证
  • 【网络篇】计算机网络——链路层详述(笔记)