9、Spring之代理模式

这篇具有很好参考价值的文章主要介绍了9、Spring之代理模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

9.1、环境搭建

9.1.1、创建module

9、Spring之代理模式

9.1.2、选择maven

9、Spring之代理模式

9.1.3、设置module名称和路径

9、Spring之代理模式

9、Spring之代理模式

9.1.4、module初始状态

9、Spring之代理模式

9.1.5、配置打包方式和依赖

9、Spring之代理模式

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.rain</groupId>
    <artifactId>spring_proxy</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <!-- junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>


</project>

9.2、场景模拟

9.2.1、创建Calculator接口及实现类

9、Spring之代理模式

package org.rain.spring.proxy;

/**
 * @author liaojy
 * @date 2023/8/6 - 23:53
 */
public interface Calculator {

    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);

}

9、Spring之代理模式

package org.rain.spring.proxy;

/**
 * @author liaojy
 * @date 2023/8/6 - 23:55
 */
public class CalculatorImpl implements Calculator {
    public int add(int i, int j) {

        int result = i + j;
        System.out.println("方法内部 result = " + result);
        return result;

    }

    public int sub(int i, int j) {

        int result = i - j;
        System.out.println("方法内部 result = " + result);
        return result;

    }

    public int mul(int i, int j) {

        int result = i * j;
        System.out.println("方法内部 result = " + result);
        return result;

    }

    public int div(int i, int j) {

        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;

    }
}

9.2.2、为Calculator实现类增加日志功能

9、Spring之代理模式

package org.rain.spring.proxy;

/**
 * @author liaojy
 * @date 2023/8/6 - 23:55
 */
public class CalculatorImpl implements Calculator {
    public int add(int i, int j) {

        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] add 方法结束了,结果是:" + result);
        return result;

    }

    public int sub(int i, int j) {

        System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] sub 方法结束了,结果是:" + result);
        return result;

    }

    public int mul(int i, int j) {

        System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] mul 方法结束了,结果是:" + result);
        return result;

    }

    public int div(int i, int j) {

        System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] div 方法结束了,结果是:" + result);
        return result;

    }
}

9.3、场景分析

9.3.1、代码缺陷

关于带日志功能的实现类,有如下缺陷:

  • 附加功能对核心业务功能有干扰,降低了开发效率

  • 附加功能分散在各个业务功能方法中,不利于统一维护

9.3.2、解决思路

解决这两个问题,核心方式就是:解耦;把附加功能从业务功能代码中抽取出来

9.3.3、技术难点

要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决,因此需要引入新的技术(代理模式)。

9.4、代理模式的概述

9.4.1、概念

  • 代理模式是二十三种设计模式中的一种,属于结构型模式

  • 它的思想就是在不改动目标方法代码的基础上,增强目标方法的功能

  • 它的实现就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用目标方法

  • 它的作用就是把不属于目标方法核心逻辑的代码从目标方法中剥离出来,从而实现解耦和统一维护

9.4.2、术语

  • 目标:封装了核心功能代码,的类、对象、方法

  • 代理:封装了增强功能代码、且能调用目标,的类、对象、方法

9.4.3、生活中的目标和代理

  • 广告商找大明星(目标)拍广告,需要经过经纪人(代理)

  • 买房者找卖房者(目标)购房,需要经过房产中介(代理)

9.5、静态代理

先将实现类CalculatorImpl还原为没有增加日志功能的状态,即9.2.1小节的状态

9.5.1、创建静态代理类CalculatorStaticProxy

9、Spring之代理模式

注意:代理类和目标类要实现相同的接口,这样能保证它们有相同的方法列表

package org.rain.spring.proxy;

/**
 * @author liaojy
 * @date 2023/8/7 - 12:56
 */
public class CalculatorStaticProxy implements Calculator {

    // 将被代理的目标对象声明为成员变量
    private Calculator target;

    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }

    public int add(int i, int j) {
        // 附加功能由代理类中的代理方法来实现
        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
        // 通过目标对象来实现核心业务逻辑
        int addResult = target.add(i, j);
        System.out.println("[日志] add 方法结束了,结果是:" + addResult);
        return addResult;
    }

    public int sub(int i, int j) {
        System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
        int subResult = target.sub(i, j);
        System.out.println("[日志] sub 方法结束了,结果是:" + subResult);
        return subResult;
    }

    public int mul(int i, int j) {
        System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
        int mulResult = target.mul(i, j);
        System.out.println("[日志] mul 方法结束了,结果是:" + mulResult);
        return mulResult;
    }

    public int div(int i, int j) {
        System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
        int divResult = target.div(i, j);
        System.out.println("[日志] div 方法结束了,结果是:" + divResult);
        return divResult;
    }
}

9.5.2、测试

9、Spring之代理模式

package org.rain.spring.test;

import org.junit.Test;
import org.rain.spring.proxy.CalculatorImpl;
import org.rain.spring.proxy.CalculatorStaticProxy;

/**
 * @author liaojy
 * @date 2023/8/7 - 14:12
 */
public class ProxyTest {

    @Test
    public void testStaticProxy(){
        CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(new CalculatorImpl());
        int addResult = calculatorStaticProxy.add(1, 2);
        System.out.println(addResult);
    }

}

9.5.3、静态代理的缺点

  • 静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性

  • 当其他目标类也需要附加日志,就得创建更多静态代理类,还是产生了大量重复的代码;而且日志功能还是分散的,没有统一管理

9.6、动态代理

动态代理的意思是,在代码运行的过程中动态地生成目标类的代理类

9.6.1、创建生成代理对象的工厂类ProxyFactory

9、Spring之代理模式

比起实现固定接口方法的静态代理,动态代理的关键是能动态获取并实现目标的接口方法;
因此动态代理能对任意目标对象的核心业务方法(接口方法)进行增强

package org.rain.spring.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * @author liaojy
 * @date 2023/8/7 - 23:07
 */
//这个类不是一个代理类而是一个工具(工厂)类,用于动态生成目标对象的代理对象
public class ProxyFactory {

    //因为被代理的目标对象是任意的,所以目标对象变量的类型设为Object
    private Object target;

    //通过工厂类的有参构造方法,对目标对象变量进行赋值
    public ProxyFactory(Object target) {
        this.target = target;
    }

    //生成任意目标对象所对应的代理对象;因为不确定动态生成的代理对象的类型,所以返回值设为Object
    public Object getPoxy(){

        //通过目标对象获取应用类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();

        //获取目标对象实现的所有接口的class对象所组成的数组
        Class<?>[] interfaces = target.getClass().getInterfaces();

        //通过InvocationHandler的匿名内部类,来设置代理类中如何重写接口中的抽象方法
        InvocationHandler invocationHandler = new InvocationHandler() {

            //通过invoke方法来统一管理代理类中的方法该如何执行,该方法有三个参数
            /**
             * @param proxy:表示代理对象
             * @param method:表示要执行的方法
             * @param args:表示要执行的方法的参数列表
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //在调用目标对象执行功能之前,加入额外的操作(这里是附加日志功能)
                System.out.println("[日志] "+method.getName()+" 方法开始了,参数是:" + Arrays.toString(args));

                //固定写法:调用目标对象实现的核心逻辑(最重要的步骤)
                Object result = method.invoke(target, args);

                //在调用目标对象执行功能之后,加入额外的操作(这里是附加日志功能)
                System.out.println("[日志] "+method.getName()+" 方法结束了,结果是:" + result);

                //固定写法:保证代理对象和目标对象的返回值一致
                return result;
            }

        };

        //返回(java.lang.reflect包下的)Proxy类的newProxyInstance方法所生产的代理对象
        /**
         * newProxyInstance方法有三个参数:
         *
         * 1、ClassLoader classLoader:指定加载(动态生成的)代理类的类加载器
         *    类只有被加载后才能使用,(动态生成的)代理类需要用应用类加载器来加载
         *    类加载器有四种:
         *      跟类加载器(用于加载核心类库)
         *      扩展类加载器(用于加载扩展类库)
         *      应用类加载器(用于加载自己写的类或第三方jar包中的类)
         *      自定义类加载器
         *
         * 2、Class<?>[] interfaces:指定代理对象要实现的接口
         *    这个参数用于保证代理对象和目标对象有相同的方法列表
         *
         * 3、InvocationHandler invocationHandle:指定调用处理器
         *    该处理器设置了代理对象实现的接口的方法被调用时,该如何执行
         */
        return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);

    }
}

9.6.2、测试

9、Spring之代理模式

    @Test
    public void testDynamicProxy(){

        //根据目标对象来创建(动态)代理对象的工厂
        ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());

        //通过(动态)代理对象的工厂,生成目标对象所对应的(动态)代理对象
        //因为代理类是动态生成的,所以不确定代理类的类型,因此用其所实现的接口类型
        Calculator poxy = (Calculator) proxyFactory.getPoxy();

        //调用动态代理对象的方法,该方法是目标对象核心业务方法的增强方法
        int addResult = poxy.add(1, 2);
        System.out.println(addResult);

    }

9.6.3、增强的位置

除了可以在调用目标对象执行功能之前或之后,加入额外的操作之外;

还可以在调用目标对象执行功能发生异常时(catch位置)或在调用目标对象执行功能完毕时(finally位置),加入额外的操作

也就是说,(静态或动态)代理能增强的位置一共有四个

            //通过invoke方法来统一管理代理类中的方法该如何执行,该方法有三个参数
            /**
             * @param proxy:表示代理对象
             * @param method:表示要执行的方法
             * @param args:表示要执行的方法的参数列表
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    //第1个增强位置:在调用目标对象执行功能之前,加入额外的操作(这里是附加日志功能)
                    System.out.println("[日志] "+method.getName()+" 方法开始了,参数是:" + Arrays.toString(args));

                    //固定写法:调用目标对象实现的核心逻辑(最重要的步骤)
                    result = method.invoke(target, args);

                    //第2个增强位置:在调用目标对象执行功能之后,加入额外的操作(这里是附加日志功能)
                    System.out.println("[日志] "+method.getName()+" 方法结束了,结果是:" + result);
                } catch (Exception e) {
                    //第3个增强位置:在调用目标对象执行功能发生异常时,加入额外的操作(这里是附加日志功能)
                    System.out.println("[日志] "+method.getName()+",异常:"+e.getMessage());
                }  finally {
                    //第4个增强位置:在调用目标对象执行功能完毕时,加入额外的操作(这里是附加日志功能)
                    System.out.println("[日志] "+method.getName()+",方法执行完毕");
                }

                //固定写法:保证代理对象和目标对象的返回值一致
                return result;
            }

9.6.4、扩展知识

  • 动态代理有两种方式:jdk动态代理(本示例)和cglib动态代理

  • jdk动态代理,要求目标必须实现接口,而且只能对目标所实现的接口方法进行增强

  • jdk动态代理,生成的代理类在com.sun.proxy包下,类名为:$proxy+数字

  • cglib动态代理,不要求目标必须实现接口,生成的代理类会继承目标类,并且和目标类在相同的包下

  • 虽然在实际中很少写动态代理的代码,但了解动态代理的思想,对学习Spring的AOP知识很有帮助文章来源地址https://www.toymoban.com/news/detail-633651.html

到了这里,关于9、Spring之代理模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 54.Spring的AOP是在哪里创建的动态代理?

    正常的Bean会在Bean的生命周期的‘初始化’后, 通过BeanPostProcessor.postProcessAfterInitialization创建aop的动态代理 还有一种特殊情况: 循环依赖的Bean会在Bean的生命周期‘属性注入’时存在的循环依赖的情况下, 也会为循环依赖的Bean 通过MergedBeanDefinitionPostProcessor.postProcessMergedBe

    2024年02月02日
    浏览(10)
  • Java 设计者模式以及与Spring关系(四) 代理模式

    Java 设计者模式以及与Spring关系(四) 代理模式

    目录 简介: 23设计者模式以及重点模式 代理模式(Proxy Pattern) 静态代理示例 spring中应用 动态代理 1.基于JDK的动态代理 target.getClass().getInterfaces()作用 内名内部类写法(更简洁,但不推荐) 2.基于CGLIB实现 spring中应用 本文是个系列一次会出两个设计者模式作用,如果有关联就三

    2024年01月23日
    浏览(48)
  • Spring AOP (面向切面编程)原理与代理模式—实例演示

    Spring AOP (面向切面编程)原理与代理模式—实例演示

    Spring 中文文档 (springdoc.cn) Spring | Home 官网         Java是一个面向对象(OOP)的语言,但它有一些弊端。虽然使用OOP可以通过组合或继承的方式来实现代码的重用。但当我们需要为多个不具有继承关系的对象(一般指的是两个不同的类,它们之间没有继承自同一个父类或接

    2024年02月15日
    浏览(10)
  • pycharm+selenium搭建环境之no module named ‘selenium‘异常解决

    pycharm+selenium搭建环境之no module named ‘selenium‘异常解决

    1.  检查是否安装了selenium   a)pip  show  selenium  发现自己已经安装了selenium,重新执行还是报异常; b)上面a)检查完毕了,但还是不行那么就要检查自己的项目是不是放在安装Python的路径下了? 例如我 Python装在D盘,项目创建在E盘,那明显selenium就没有安装在项目下,此时

    2024年02月16日
    浏览(11)
  • Nginx + RTMP + nginx-http-flv-module 环境搭建(CentOS 7)

    Nginx + RTMP + nginx-http-flv-module 环境搭建(CentOS 7)

    🎉Nginx + RTMP + nginx-http-flv-module 环境搭建(CentOS 7) ☆* o(≧▽≦)o *☆嗨~我是IT·陈寒🍹 ✨博客主页:IT·陈寒的博客 🎈该系列文章专栏:Java学习路线 📜其他专栏:Java学习路线 Java面试技巧 Java实战项目 AIGC人工智能 数据结构学习 🍹文章作者技术和水平有限,如果文中出现

    2024年02月05日
    浏览(33)
  • Linux-tomcat环境搭建、jpress部署实践、nginx反向代理

    ♥️ 作者:小刘在C站 ♥️ 个人主页:  小刘主页  ♥️ 努力不一定有回报,但一定会有收获加油!一起努力,共赴美好人生! ♥️ 学习两年总结出的运维经验,以及思科模拟器全套网络实验教程。专栏: 云计算技术 ♥️小刘私信可以随便问,只要会绝不吝啬,感谢CSD

    2024年02月16日
    浏览(13)
  • 项目创建第一天 搭建前端环境

    项目创建第一天 搭建前端环境

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 提示:这里可以添加本文要记录的大概内容: 前端环境 Hbuider x + elementui 转发 nginx 后台环境 springcloud 提示:以下是本篇文章正文内容,下面案例可供参考 示例:构建代码的土壤 导入 安装路由 在根目录

    2024年02月03日
    浏览(10)
  • 【环境搭建】使用IDEA创建快速搭建SpringBoot项目详细步骤

    【环境搭建】使用IDEA创建快速搭建SpringBoot项目详细步骤

    环境准备: 开发工具IDE:IntelliJ IDEA 2022.2.4 (Ultimate Edition) 开发环境JDK:Oracle OpenJDK Version 1.8.0_162 构建工具MAVEN: Maven 3.6.3 (1)创建spring initializr工程 通过IntelliJ IDEA工具创建工程时,通过file-new-project后,在弹出的界面中选择spring initializr选项(社区版无此选项)。然后去勾选相关

    2024年02月03日
    浏览(12)
  • 【YOLOv7-环境搭建】PyTorch安装后输出版本显示No module named ‘Torch’的解决方法

    【YOLOv7-环境搭建】PyTorch安装后输出版本显示No module named ‘Torch’的解决方法

    可能一:P yCharm环境导入错误    配置的解释器,必须为所创建的虚拟环境下的python.exe文件,别的路径下的python.exe文件不好使!! 解决方法:根据【YOLOv7-环境搭建③】PyCharm安装和环境、解释器配置文中配置解释器的步骤进行检查与更正 可能二:缺少Torch包    既然显示没有

    2024年02月15日
    浏览(10)
  • 保姆级教学——集群环境搭建及创建集群

    保姆级教学——集群环境搭建及创建集群

    一些默认,加载镜像开启虚拟机,在安装位置选择自己目录,然后建立分区,首先添加 挂载点,类型标准分区,文件系统ext4 加载分区,期望给2G, 类型标准分区,swap默认 然后剩下47G给/就可以 网络打开,把kudmp对勾去掉 安装完成重新启动 查看自己的ip地址ip addr,我的是192

    2024年02月08日
    浏览(10)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包