知识点:代理设计模式

ops/2024/10/22 2:53:54/

1.场景设定和问题复现

   1 准备项目

       pom.xml

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.3.1</version>
    <scope>test</scope>
</dependency>

   2. 声明功能接口

/**
 * 功能接口
 */
public interface GongNeng {
    //吃饭
    void chifan();
    //谈小目标
    void tanxiaomb();
}

3.声明老总目标类实现功能接口

/**
 * 老总:目标对象类实现功能接口
 */
public class LaoZong implements GongNeng {
    @Override
    public void chifan() {
        //核心功能
        System.out.println("老总吃饭...");
    }

    @Override
    public void tanxiaomb() {
        //核心功能
        System.out.println("老总谈一个亿的小目标...");
    }
}

  

4.声明带预约和联系方式附加功能实现

   新需求: 需要在每个方法中,添加控制台输出,输出预约和输出留个联系方式,方便下次联系!

/**
 * 老总:目标对象类
 * 需要在每个方法中,添加控制台输出,输出预约和输出留个联系方式,方便下次联系!
 */
public class LaoZong implements GongNeng {
    @Override
    public void chifan() {
        System.out.println("预约....");
        //核心功能
        System.out.println("老总吃饭...");
        System.out.println("留个联系方式,方便下次联系....");
    }

    @Override
    public void tanxiaomb() {
        System.out.println("预约....");
        //核心功能
        System.out.println("老总谈一个亿的小目标...");
        System.out.println("留个联系方式,方便下次联系....");
    }
}

   

5.代码问题分析

1. 代码缺陷

    - 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力

    - 附加功能代码重复,分散在各个业务功能方法中!冗余,且不方便统一维护!

2. 解决思路

      核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。

      将重复的代码统一提取,并且[[动态插入]]到每个业务方法!

3. 技术困难

    解决问题的困难:提取重复附加功能代码到一个类中,可以实现

    但是如何将代码插入到各个方法中,我们不会,我们需要引用新技术!!!

6.解决技术代理模式

   1. 代理模式

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

  2. 无代理场景

  3. 有代理场景

   4. 生活中的代理

- 广告商找大明星拍广告需要经过经纪人

- 合作伙伴找大老板谈合作要约见面时间需要经过秘书

- 房产中介是买卖双方的代理

- 太监是大臣和皇上之间的代理

  5. 相关术语

- 代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。(中介)

   动词:指做代理这个动作,或这项工作

   名词:扮演代理这个角色的类、对象、方法

- 目标:**被代理**“套用”了核心逻辑代码的类、对象、方法。(房东)

代理在开发中实现的方式具体有两种:静态代理,[动态代理技术]

   6.静态代理实现

     1. 主动创建代理类

/**
 * 秘书:静态代理类
 */
public class XiaoMi implements GongNeng{
    //将被代理的目标对象:老总
    LaoZong laoZong;

    public XiaoMi(LaoZong laoZong) {
        this.laoZong = laoZong;
    }

    @Override
    public void chifan() {
        //附加功能由代理类中的代理方法来实现:核心业务前执行的操作
        System.out.println("预约....");
        //调用目标对象老总的吃饭方法
        laoZong.chifan();
        //附加功能由代理类中的代理方法来实现:核心业务后执行的操作
        System.out.println("留个联系方式,方便下次联系....");
    }

    @Override
    public void tanxiaomb() {
        //附加
        System.out.println("预约....");
        laoZong.tanxiaomb();
        //附加
        System.out.println("留个联系方式,方便下次联系....");
    }
}

  测试代码

@Test
public void  testStaticProxy(){
    //创建老总对象:马云
    LaoZong yunyun=new LaoZong();
    //创建云云的秘书
    XiaoMi xm=new XiaoMi(yunyun);
    //吃饭
    xm.chifan();
    System.out.println("--------------");
    xm.tanxiaomb();
}

    静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。

7.动态代理

   1. 动态代理技术分类

动态代理是一种在运行时动态生成代理对象的技术。它是一种设计模式,用于在不修改原始对象的情况下,通过代理对象来间接访问原始对象,并在访问前后执行额外的操作。

JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口!他会根据目标类的接口动态生成一个代理对象!代理对象和目对象有标相同的接口!(拜把子)

cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口!(认干爹)

   2.基于jdk代理技术实现

        1.声明功能接口

/**
 * 功能接口
 */
public interface GongNeng {
    //吃饭
    void chifan();
    //谈小目标
    void tanxiaomb();
}

        2.声明老总目标类实现功能接口

/**
 * 老总:目标对象类实现功能接口
 */
public class LaoZong implements GongNeng {
    @Override
    public void chifan() {
        //核心功能
        System.out.println("老总吃饭...");
    }

    @Override
    public void tanxiaomb() {
        //核心功能
        System.out.println("老总谈一个亿的小目标...");
    }
}

   

          3.定义jdk动态代理工厂类,生成动态代理对象和目标对象实现同一个接口,并调用代理方法invoke

/**
 * jdk动态代理工厂类,生成小秘动态代理对象
 */
public class JdkProxyHandler implements InvocationHandler {
    //代理的目标对象
    LaoZong laoZong;

    public JdkProxyHandler(LaoZong laoZong) {
        this.laoZong = laoZong;
    }

    /**
     * invoke()设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法
     * proxy:代理对象
     * method:代理对象需要实现的方法,即其中需要重写的方法
     * argsmethod所对应方法的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("预约....");
        //调用目标对象的方法
        Object obj=method.invoke(laoZong,args);
        System.out.println("留下联系方式,方便下次联系....");
        return obj;
    }
}

           4. 测试代码

/**
 * JDK动态代理
 */
@Test
public void testJdkProxy(){
    //创建老总对象
    LaoZong yunyun=new LaoZong();
    /**
     * 创建代理对象,通过jdk动态代理生成代理对象的方法
     * Proxy.newProxyInstance():创建一个代理实例
     * 其中有三个参数:
     * 1classLoader:加载动态生成的代理类的类加载器
     * 2interfaces:目标对象实现的所有接口的class对象所组成的数组
     * 3invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法
     */
    GongNeng xm=(GongNeng)Proxy.newProxyInstance(TestProxy.class.getClassLoader(),new Class[]{GongNeng.class},new JdkProxyHandler(yunyun));
    //通过代理对象调用目标对象的方法,从而扩展附加功能
    xm.chifan();
    System.out.println("--------------");
    xm.tanxiaomb();
}

 

3. 基于cglib代理技术,生成动态代理对象

   1.引入cglib坐标

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

   2.定义目标对象的类 (不要实现接口)

/**
 * 老总:目标对象类
 */
public class LaoZong {
    public void chifan() {
        //核心功能
        System.out.println("老总吃饭...");
    }
    public void tanxiaomb() {
        //核心功能
        System.out.println("老总谈一个亿的小目标...");
    }
}

    3.定义cglib动态代理工厂类,生成动态代理对象,是目标对象的子类。

      并调用代理方法invoke

/**
 * cglib动态代理工厂类,生成日志动态代理对象
 */
public class CglibInterceptor implements MethodInterceptor {
    /**
     * intercept设置代理对象实现目标对象方法的过程,即代理类中如何重写目标中的方法
     * o :目标对象
     * method:目标对象的方法
     * MethodProxy:代理对象的方法
     * objectsmethod所对应方法的参数
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("预约....");
        //调用目标对象的方法
        Object obj=methodProxy.invokeSuper(o,objects);
        System.out.println("留下联系方式,方便下次联系....");
        return obj;
    }
}

      3. 测试代码

/**
 * Cglib动态代理
 */
@Test
public void testCglibProxy(){
    //创建代理对象,是目标对象的子类
    Enhancer e=new Enhancer();
    //1.指定父类:目标对象
    e.setSuperclass(LaoZong.class);
    //2.指定callback,指定代理对象调用一个对象的代理方法
    e.setCallback(new CglibInterceptor());
    LaoZong xm=(LaoZong) e.create();
    //通过代理对象调用目标对象的方法,从而扩展附加功能
    xm.chifan();
    System.out.println("--------------");
    xm.tanxiaomb();
}

     注意:运行时会出现异常,原因由于jdk8之后的版本的反射相关功能被限制,导致了异常

     解决方案:编辑配置时手动添加两个参数实现jdk的兼容,选择开启不被允许的反射功能。--add-opens java.base/java.lang=ALL-UNNAMED

   8.代理总结

**代理方式可以解决附加功能代码干扰核心代码和不方便统一维护的问题!**

他主要是将附加功能代码提取到代理中执行,不干扰目标核心代码!

但是我们也发现,无论使用静态代理和动态代理(jdk,cglib),程序员的工作都比较繁琐!

需要自己编写代理工厂等!

但是,提前剧透,我们在实际开发中,不需要编写代理代码,我们可以使用[Spring AOP]框架,他会简化动态代理的实现!!!


http://www.ppmy.cn/ops/127456.html

相关文章

医护人员排班|基于springBoot的医护人员排班系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息…

从 K8s 的 “临时容器” 看 K8s 设计的厉害之处

大家好&#xff0c;这里是G-LAB IT实验室。 ​ 从一个容器的不足说起 容器概念出现时&#xff0c;有个非常重要的理念&#xff1a;容器中极简。 即容器里面只保留需要运行的进程就可以&#xff0c;其他一律不要安装。这也是为什么 Docker 出现的那时&#xff0c;有一篇文章…

Windows PowerShell 有没有类似conda的虚拟环境功能?

PowerShell本身并不直接提供与Conda完全相同的环境功能&#xff0c;但PowerShell可以通过一些方法和工具来实现类似的环境管理。以下是对PowerShell和Conda环境功能的详细对比及PowerShell实现类似功能的途径&#xff1a; 一、Conda的环境功能 Conda是一个开源的包管理系统和…

Mac 编译 Unreal 源码版本

在Mac上编译Unreal Engine源码需要遵循以下步骤&#xff1a; 安装必要的依赖项&#xff1a; Xcode Python&#xff08;建议使用2.7版本&#xff09; Java&#xff08;使用JDK 8&#xff09; CMake Ninja SVN&#xff08;用于获取某些依赖项&#xff09; 获取Unreal Engi…

15分钟学Go 实战项目一:命令行工具

实战项目一&#xff1a;命令行工具 1. 引言 命令行工具是开发者常用的工具之一&#xff0c;它可以帮助用户通过命令行界面对程序进行控制和交互。在这节中&#xff0c;我们将创建一个简单的命令行工具&#xff0c;以帮助你理解Go语言的基本语法和如何处理命令行输入。在这个过…

数据库相关操作

1. 创建数据库 首先&#xff0c;使用 CREATE DATABASE 语句来创建一个新的数据库。 CREATE DATABASE my_database; 2. 使用数据库 创建数据库后&#xff0c;使用 USE 语句切换到这个数据库。 USE my_database; 3. 创建表 接下来&#xff0c;在数据库中创建一张表。表中…

基于PHP在线小说阅读平台【附源码】

基于PHP在线小说阅读平台 效果如下&#xff1a; 系统首页界面 系统注册界面 热门小说详细页面 公告信息详细页面 后台登录界面 管理员主界面 用户界面 作者界面 小说分类界面 我的书架界面 作者主界面 研究背景 随着互联网的普及和数字化技术的飞速发展&#xff0c;传统的纸…

redis的发布订阅模式

1.发布订阅模式的结构 结合上图和消息中间件&#xff0c;可以将channel和消息中间件中的topic主题对应起来 2. Redis发布订阅功能 &#xff08;1&#xff09;发送消息 Redis采用PUBLISH命令发送消息&#xff0c;其返回值为接收到该消息的订阅者的数量。 &#xff08;2&#xf…