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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Spring 第四节 AOP——动态代理 万字详解!

目录

一、前言

二、动态代理快速入门

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

       2.动态代理使用案例:

       3.动态代理的灵活性 :

三、深入动态代理

       1.需求 :

       2.实现 :

           2.1 接口和实现类

           2.2 提供代理对象的类

           2.3 测试类

       3.引出AOP :  

四、总结


一、前言

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

二、动态代理快速入门

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

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

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

       2.动态代理使用案例:

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

image.gif 编辑

              Animal接口代码如下 :

package com.cyan.spring.dynamic_proxy;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public interface Animal {
    public abstract void eat();
}

image.gif

               Cat类代码如下 :  

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

image.gif

               Dog类代码如下 :

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

image.gif

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

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

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

image.gif

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

               TestAnimal类代码如下 :

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

image.gif

               运行结果 :

image.gif 编辑

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

image.gif 编辑

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

image.gif 编辑

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

image.gif 编辑

       3.动态代理的灵活性 :

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

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

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

image.gif 编辑

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

image.gif 编辑

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

image.gif 编辑

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

               testSleep()方法代码如下 :

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

image.gif

               运行结果 :

image.gif 编辑


三、深入动态代理

       1.需求 :

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

       2.实现 :

           2.1 接口和实现类

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

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

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

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

image.gif

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

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

image.gif

           2.2 提供代理对象的类

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

               CalculatorProxyProvider类代码如下 :

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

image.gif

           2.3 测试类

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

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

image.gif

               运行结果 :

image.gif 编辑

image.gif 编辑

       3.引出AOP :

               注意——

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

image.gif 编辑

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

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

image.gif 编辑

               从AOP的角度来看,也称为一个“横切关注点”,但也叫“返回通知

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

image.gif 编辑

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

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

image.gif 编辑

               从AOP看,也称为一个“横切关注点”,但又称为“后置通知

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

image.gif 编辑

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

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


四、总结

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

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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
打赏
0
23
24
0
229
分享
相关文章
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
125 6
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
111 25
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
95 8
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
116 2
Spring Aop该如何使用
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
117 5
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
123 8
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
4月前
|
深入调查研究Spring AOP
【11月更文挑战第15天】
67 5
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
86 4
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
197 5
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等