Spring AOP—深入动态代理 万字详解(通俗易懂)

目录

一、前言

二、动态代理快速入门

[1.为什么需要动态代理? :](#1.为什么需要动态代理? :)

2.动态代理使用案例:

[3.动态代理的灵活性 :](#3.动态代理的灵活性 :)

三、深入动态代理

[1.需求 :](#1.需求 :)

[2.实现 :](#2.实现 :)

[2.1 接口和实现类](#2.1 接口和实现类)

[2.2 提供代理对象的类](#2.2 提供代理对象的类)

[2.3 测试类](#2.3 测试类)

[3.引出AOP :](#3.引出AOP :)

四、总结


一、前言

  • 第四节内容,up打算和大家分享Spring 动态代理 相关的内容动态代理本质上是Spring AOP的一个前置的引入内容,一个AOP开篇之作,但它却相当重要,且本身难度较大
  • 注意事项------①代码中的注释也很重要;不要眼高手低,自己跟着过一遍才真正有收获;点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
  • 良工不示人以朴,up所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。感谢阅读!

二、动态代理快速入门

1.为什么需要动态代理? :

在日常开发中,往往存在这样一种需求 ------同时存在多个对象,这些对象对应的类都实现了同一接口,并且这些对象会去调用这个接口中的某一个方法,即++多态++,但是我们要求这几个对象在调用方法前,和调用方法后都要做一些业务处理,eg : 权限校验、事务管理、日志管理、安全校验等。

如果我们将这些相同的业务处理,都下沉到每一个具体的类,就会造成代码冗余 ,并且没有办法进行对象的统一管理和调用;而动态代理的出现,尤对其症地解决了这个问题。

2.动态代理使用案例:

up先在dynamic_proxy包下创建Animal接口 ,以及Cat, Dog类,Cat类和Dog类都实现了Animal接口。如下图所示 :

Animal接口代码如下 :

java 复制代码
package com.cyan.spring.dynamic_proxy;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public interface Animal {
    public abstract void eat();
}

Cat类代码如下 :

java 复制代码
package com.cyan.spring.dynamic_proxy;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Cat implements Animal{
    @Override
    public void eat() {
        System.out.println("小猫爱吃鱼捏~");
    }
}

Dog类代码如下 :

java 复制代码
package com.cyan.spring.dynamic_proxy;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Dog implements Animal{
    @Override
    public void eat() {
        System.out.println("小狗爱吃骨头捏~");
    }
}

然后,我们创建一个AnimalProxyProvider类,见名知意,这个类可以提供一个Animal接口的代理对象,所以,该类中肯定会定义一个方法,用来返回Animal接口的代理实例,当然,这个方法稍微有点复杂,大家可以借助up的代码注释逐渐理解。

AnimalProxyProvider类代码如下 : (尤其注意匿名内部类中实现的invoke方法,其中的两条输出语句表示实现类对象相同的业务逻辑代码

java 复制代码
package com.cyan.spring.dynamic_proxy;

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

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class AnimalProxyProvider {
    //利用接口类型对传入的对象做接收 (多态)
    private Animal targetAnimal;

    //通过带参构造传入一个Animal接口实现类的对象
    public AnimalProxyProvider(Animal targetAnimal) {
        this.targetAnimal = targetAnimal;
    }

    //编写一个方法,用于返回代理对象 (用到反射机制)
    public Animal getAnimalProxy() {
        /**
         java.lang.reflect.Proxy类中的 newProxyInstance方法可以返回一个代理对象。
         public static Object newProxyInstance(ClassLoader loader,
             Class<?>[] interfaces,
             InvocationHandler h)
             throws IllegalArgumentException {...}

            该方法需要传入三个实参 ---------
            (1) ClassLoader loader : 类加载器
            (2) Class<?>[] interfaces : 接口信息
            (3) InvocationHandler h : 调用处理器,其本身也是一个接口,内部声明了抽象方法invoke。
         */

        //(1)得到类加载器
        ClassLoader classLoader = targetAnimal.getClass().getClassLoader();

        //(2)得到被执行对象的接口信息(因为newProxyInstance方法底层是通过接口来调用的,即接口多态)
        Class<?>[] interfaces = targetAnimal.getClass().getInterfaces();

        //(3)得到处理器对象(通过匿名内部类实现,最终返回的是一个匿名内部类对象)
        //!!![注意,处理器对象本身也是newProxyInstance方法的一个形参]
        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * @param proxy the proxy instance that the method was invoked on
             *
             * @param method the {@code Method} instance corresponding to
             * the interface method invoked on the proxy instance.  The declaring
             * class of the {@code Method} object will be the interface that
             * the method was declared in, which may be a superinterface of the
             * proxy interface that the proxy class inherits the method through.
             *
             * @param args an array of objects containing the values of the
             * arguments passed in the method invocation on the proxy instance,
             * or {@code null} if interface method takes no arguments.
             * Arguments of primitive types are wrapped in instances of the
             * appropriate primitive wrapper class, such as
             * {@code java.lang.Integer} or {@code java.lang.Boolean}.
             *
             * @return : the results of method instance invoked
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /*
                    将相同的业务逻辑代码,放在处理器对象的invoke对象中,
                    避免了代码下沉到每个实现类所造成的代码冗余。
                 */
                System.out.println("要吃饭的嘛~");

                //通过反射调用实现类中的方法
                Object results = method.invoke(targetAnimal, args);

                System.out.println("吃完了捏~");

                return results;
            }
        };

        Animal animalProxy = (Animal) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return animalProxy;
    }
}

接着,up定义一个测试类,在测试类中定义一个单元测试方法,用以测试我们的动态代理是否生效。

TestAnimal类代码如下 :

java 复制代码
package com.cyan.spring.dynamic_proxy;

import org.junit.jupiter.api.Test;

public class TestAnimal {
    @Test
    public void testGeyAnimalProxy() {
        //构造接口多态
        Animal animal = new Cat();
        Animal animal2 = new Dog();

        //传入需要被代理的对象
        AnimalProxyProvider animalProxyProvider = new AnimalProxyProvider(animal);
        AnimalProxyProvider animalProxyProvider2 = new AnimalProxyProvider(animal2);

        //得到代理对象
        /*
            注意!
            此处代理对象animalProxy的编译类型仍然是Animal类型,但运行类型却不再是Cat or Dog类型,
            而是代理类型 --------- class com.sun.proxy.$Proxy9。
         */
        Animal animalProxy = animalProxyProvider.getAnimalProxy();
        Animal animalProxy2 = animalProxyProvider2.getAnimalProxy();
        System.out.println("animalProxy's RuntimeType = " + animalProxy.getClass());
        System.out.println("animalProxy2's RuntimeType = " + animalProxy2.getClass());

        //通过代理对象调用实现类方法
        animalProxy.eat();
        System.out.println("==============================");
        animalProxy2.eat();
    }
}

运行结果 :

现在我们进行Debug断点调试,断点如下图所示 :

跳入eat()方法时会发现,IDEA直接跳到了 AnimalProxyProvider类的getAnimalProxy方法中------匿名内部类实现的invoke方法里面,如下图所示 :

再往下执行便是通过反射调用 对应的method,一直往下追,最终会跳到实现类的eat()方法中,如下图所示 :

3.动态代理的灵活性 :

动态代理的灵活性体现在哪里?

首先,被代理的对象是可变的。并且,代理对象所调用的方法也是可变的

比方说,up在Animal接口中新定义了一个sleep方法,如下图所示 :

然后,up在Cat类中实现了sleep方法,如下图所示 :

接着,修改匿名内部类实现的invoke方法中的"业务逻辑"代码,如下图所示 :

最后,up在测试类中新定义一个单元测试方法,测试动态代理是否生效。

testSleep()方法代码如下 :

java 复制代码
    @Test
    public void testSleep() {
        //1.构造接口多态
        Animal animal = new Cat();

        //2.传入需要被代理的对象
        AnimalProxyProvider animalProxyProvider = new AnimalProxyProvider(animal);

        //3.获取代理对象
        Animal animalProxy = animalProxyProvider.getAnimalProxy();

        //4.通过代理对象调用实现类方法
        String sleepMinutes = animalProxy.sleep(Long.valueOf(1314));
    }

运行结果 :


三、深入动态代理

1.需求 :

定义Calculator接口,表示一个计算器,该接口中定义有可以完成简单加减乘除运算的方法,要求在每次执行运算方法前后,都打印出运算日志(运算法则和运算参数,以及运算结果)

2.实现 :

2.1 接口和实现类

首先分析需求,既然要求在每次执行运算方法前后都打印出运算日志,显然我们会想到------仍是在匿名内部类实现的invoke方法中动手脚。

别的不说,先来定义Calculator接口和一个它的实现类

Calculator接口如下 : (声明了"加减乘除"四个抽象方法)

java 复制代码
package com.cyan.spring.dynamic_proxy;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public interface Calculator {
    public abstract double add(double n1, double n2);
    public abstract double subtract(double n1, double n2);
    public abstract double multiply(double n1, double n2);
    public abstract double divide(double n1, double n2);
}

定义一个实现类Calculator_Demo1,代码如下 :

java 复制代码
package com.cyan.spring.dynamic_proxy;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Calculator_Demo1 implements Calculator {
    @Override
    public double add(double n1, double n2) {
        return n1 + n2;
    }

    @Override
    public double subtract(double n1, double n2) {
        return n1 - n2;
    }

    @Override
    public double multiply(double n1, double n2) {
        return n1 * n2;
    }

    @Override
    public double divide(double n1, double n2) {
        //分母不允许为0
        if (n2 != 0) {
            return n1 / n2;
        }
        return -1;
    }
}

2.2 提供代理对象的类

定义一个CalculatorProxyProvider类,与上文 "动态代理使用案例" 中定义的"提供代理对象的类"类似,都需要定义一个方法用于返回代理对象。

CalculatorProxyProvider类代码如下 :

java 复制代码
package com.cyan.spring.dynamic_proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.time.LocalDateTime;

public class CalculatorProxyProvider {
    private Calculator calculator;

    public CalculatorProxyProvider(Calculator calculator) {
        this.calculator = calculator;
    }

    /*
        底层仍然使用java.lang.reflect包下的Proxy类的newProxyInstance方法来获取代理对象。
     */
    public Calculator getCalculatorProxy() {
        //1.获取newProxyInstance方法的第一个参数------------类加载器
        ClassLoader classLoader = calculator.getClass().getClassLoader();

        //2.获取newProxyInstance方法的第二个参数------------接口信息
        Class<?>[] interfaces = calculator.getClass().getInterfaces();

        //3.获取newProxyInstance方法的第三个参数------------处理器对象
            //仍然借助匿名内部类来实现,并通过构造接口多态的形式做接收。
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //获取到当前传入的参数
                double n1 = (double) args[0];
                double n2 = (double) args[1];

                //获取当前方法名
                String name = method.getName();

                //定义运算结果
                double result = 0.0;

                try {
                    //Δ在运算方法执行前打印出运算日志
                    System.out.println("运算日志------------运算法则 = " + name + ",传入两个参数分别是 " + n1 + " 和 " + n2);

                    //执行运算
                    result = (double) method.invoke(calculator, args);
                    System.out.println("result = " + result);

                    //Δ在运算方法执行后打印出运算日志
                    System.out.println("运算日志------------运算法则 = " + name + ",运算结果 = " + result);

                    //返回运算结果
                    return result;
                } catch (Exception e) {
                    System.out.println("异常日志------------" + LocalDateTime.now() + ",方法" + method.getName() + "执行异常");
                    throw new RuntimeException(e);
                } finally {
                    System.out.println("执行日志------------" + method.getName() + "方法执行结束。");
                }
            }
        };

        //4.调用newProxyInstance方法,得到代理对象
        Calculator instance = (Calculator) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        //5.返回获得的代理对象
        return instance;
    }
}

2.3 测试类

最后,仍然是在测试类中定义一个单元测试方法,up新定义了一个TestCalculator类,代码如下 :

java 复制代码
package com.cyan.spring.dynamic_proxy;

import org.junit.jupiter.api.Test;

public class TestCalculator {
    @Test
    public void testArithmetic() {
        //1.构造接口多态
        Calculator calculator = new Calculator_Demo1();

        //2.传入需要被代理的对象
        CalculatorProxyProvider calculatorProxyProvider = new CalculatorProxyProvider(calculator);

        //3.获取代理对象
        Calculator calculatorProxy = calculatorProxyProvider.getCalculatorProxy();

        //4.通过代理对象调用实现类方法
        double addResult = calculatorProxy.add(200.333, 33);
        System.out.println("-----------------------------");
        double subtractResult = calculatorProxy.subtract(141, 5);
        System.out.println("-----------------------------");
        double multiplyResult = calculatorProxy.multiply(11.11, 2);
        System.out.println("-----------------------------");
        double divideResult = calculatorProxy.divide(3917.0, 500.00);
    }
}

运行结果 :

注意------

(1) CalculatorProxyProvider类的这段代码,如下图所示 :

在AOP中,称为"++横切关注点++ ",也叫"前置通知"

**(2)**而下面的这段代码,如下图所示 :

从AOP的角度来看,也称为一个"++横切关注点++ ",但也叫"后置通知"

(3) 此外,异常处理------catch语句块中的这段代码,如下图所示 :

从AOP看,也称为一个"++横切关注点++ ",但又称为"异常通知"。

(4) 最后,finally代码块中的内容,如下图所示 :

从AOP看,也称为一个"++横切关注点++ ",但又称为"最终通知"

3.引出AOP :

分析一下我们方才写得代码,如下图所示 :

可以看到,无论是"前置通知","后置通知",还是"异常通知","最终通知"。我们都只是草草地用了一条输出语句敷衍过去,这使得我们的代码不够牛逼,功能不够强大,且代码死板,不够灵活。

而作为一名OOP程序员,我们会容易联想到------假如此处的输出语句都替换成方法,用一个方法直接切入,那不就既满足灵活性,又可以实现强大的功能吗?


四、总结

  • 🆗,以上就是Spring系列博文第四小节的全部内容了。
  • 总结一下,我们应该明白动态代理究竟"动态"在哪里?------ 不止是被代理对象可变,且代理对象执行的方法也是可变的。我们还需要知道newProxyInstance方法的三个形参分别有什么作用,以及如何获取这三个形参(尤其是第三个形参------处理器对象的获取,用到了匿名内部类)。总之,这篇博文其实是为了给"Spring---AOP"的分享做一个铺垫,我们以一个问题开始,又以一个问题结尾,也符合这篇博客的定位。
  • 下一节内容------Spring AOP---切入表达式和基于XML配置AOP,我们不见不散。感谢阅读!

System.out.println("END---------------------------------------");

相关推荐
hqxstudying4 分钟前
java依赖注入方法
java·spring·log4j·ioc·依赖
·云扬·12 分钟前
【Java源码阅读系列37】深度解读Java BufferedReader 源码
java·开发语言
春生野草31 分钟前
关于SpringMVC的整理
spring
martinzh1 小时前
Spring AI 项目介绍
后端
Bug退退退1231 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠1 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
前端付豪1 小时前
20、用 Python + API 打造终端天气预报工具(支持城市查询、天气图标、美化输出🧊
后端·python
爱学习的小学渣1 小时前
关系型数据库
后端
武子康1 小时前
大数据-33 HBase 整体架构 HMaster HRegion
大数据·后端·hbase
前端付豪1 小时前
19、用 Python + OpenAI 构建一个命令行 AI 问答助手
后端·python