首页 > 其他分享 >9、Spring之代理模式

9、Spring之代理模式

时间:2023-08-08 23:45:35浏览次数:30  
标签:int Spring 代理 模式 System result println out

9.1、环境搭建

9.1.1、创建module

image

9.1.2、选择maven

image

9.1.3、设置module名称和路径

image

image

9.1.4、module初始状态

image

9.1.5、配置打包方式和依赖

image

<?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接口及实现类

image

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);

}

image

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实现类增加日志功能

image

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

image

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

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、测试

image

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

image

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

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、测试

image

    @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知识很有帮助

标签:int,Spring,代理,模式,System,result,println,out
From: https://www.cnblogs.com/Javaer1995/p/17610379.html

相关文章

  • 遇到问题--python--爬虫--使用代理ip第二次获取代理ip失败
    情况获取代理ip的代码defferch_proxy_ips():try:api="http://dynamic.goubanjia.com/dynamic/get/12323.html?sep=3"response=urllib.request.urlopen(api,timeout=8)the_page=response.read()content=the_page.decode(&......
  • Springmvc展示oss的图片
    公开权限的图片展示首先确定思路,存储在oss中的图片有两种权限模式,一种是公开的,这种直接通过url对应到具体某张图片即可显示。格式如下:http://<yourBucketName>.<yourEndpoint>/<yourObjectName>?x-oss-process=image/<yourAction>,<yourParamValue>具体例子http://image-demo.oss-c......
  • Springboot集成使用阿里云kafka详细步骤
    明确连接认证类型首先要明确使用哪种连接认证类型Ons模式参考https://github.com/AliwareMQ/aliware-kafka-demos/tree/master/kafka-java-demo/betaOns模式的conf内容KafkaClient{com.aliyun.openservices.ons.sasl.client.OnsLoginModulerequiredAccessKey="......
  • BuilderPattern-构建器模式
    在C#中,构造器模式(BuilderPattern)是一种创建型设计模式,用于创建一个复杂对象的过程,并将其分解为多个简单步骤进行创建。与其他创建型模式(如工厂模式)不同,构造器模式着重于对象的构建过程,而不是直接创建对象。构造器模式通常由以下几个关键组件组成:产品类(Product):表示构造器模式中......
  • Typecho 反向代理 http 访问强制启用生成 https 链接
    问题描述微酷是使用Nginx反向代理内网的Typecho站点,为了效率内网访问不需要使用https,这样Typecho接收到的请求是http协议的,于是网站内部资源链接被修改成了http。解决方案分析了下源代码,最终定位到解析url依赖\var\Typecho\Request.php文件中的isSecure()函数,如下:/***判......
  • springboot集成mongo
    springboot集成mongo背景linux版本:KylinV10docker版本:18.03mongo版本:5.0.5报错信息Causedby:com.mongodb.MongoCommandException:Commandfailedwitherror18:'Authenticationfailed.'onserver172.18.48.233:8888.Thefullresponseis{"ok&quo......
  • SpringBoot静态资源
    访问顺序:Controller->静态资源->404静态资源默认访问路径前端访问:http://localhost:8080/page4.htmlclasspath:/staticclasspath:/publicclasspath:/resourcesclasspath:/META-INF/resources自定义访问路径自定义后默认访问路径失效yml配置文件配置spring: #匹配方式-即前缀 mvc......
  • Spring-1-深入理解Spring XML中的依赖注入(DI):简化Java应用程序开发
    学习目标前两篇文章我们介绍了什么是Spring,以及Spring的一些核心概念,并且快速快发一个Spring项目,以及详细讲解IOC,今天详细介绍一些DI(依赖注入)能够配置setter方式注入属性值能够配置构造方式注入属性值能够理解什么是自动装配一、依赖注入(DI配置)1依赖注入方式【重点】......
  • Spring-2-透彻理解Spring 注解方式创建Bean--IOC
    今日目标学习使用XML配置第三方Bean掌握纯注解开发定义Bean对象掌握纯注解开发IOC模式1.第三方资源配置管理说明:以管理DataSource连接池对象为例讲解第三方资源配置管理1.1XML管理Druid连接池(第三方Bean)对象【重点】数据库准备--创建数据库createdatabaseifno......
  • Spring-2-深入理解Spring 注解依赖注入(DI):简化Java应用程序开发
    今日目标掌握纯注解开发依赖注入(DI)模式学习使用纯注解进行第三方Bean注入1注解开发依赖注入(DI)【重点】问题导入思考:如何使用注解方式将Bean对象注入到类中1.1使用@Autowired注解开启自动装配模式(按类型)@ServicepublicclassStudentServiceImplimplementsStuden......