9、Spring之代理模式

9.1、环境搭建

9.1.1、创建module

9.1.2、选择maven

9.1.3、设置module名称和路径

9.1.4、module初始状态

9.1.5、配置打包方式和依赖

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

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

}

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

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

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

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

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

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

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

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

热门相关:亿万盛宠只为你   异世修真邪君   大妆   后福   大妆