• Spring实战 | Spring AOP核心秘笈之葵花宝典


    Spring实战系列文章:

    Spring实战 | Spring IOC不能说的秘密?

    国庆中秋特辑系列文章:

    国庆中秋特辑(八)Spring Boot项目如何使用JPA

    国庆中秋特辑(七)Java软件工程师常见20道编程面试题

    国庆中秋特辑(六)大学生常见30道宝藏编程面试题

    国庆中秋特辑(五)MySQL如何性能调优?下篇

    国庆中秋特辑(四)MySQL如何性能调优?上篇

    国庆中秋特辑(三)使用生成对抗网络(GAN)生成具有节日氛围的画作,深度学习框架 TensorFlow 和 Keras 来实现

    国庆中秋特辑(二)浪漫祝福方式 使用生成对抗网络(GAN)生成具有节日氛围的画作

    国庆中秋特辑(一)浪漫祝福方式 用循环神经网络(RNN)或长短时记忆网络(LSTM)生成祝福诗词

    Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架的一个重要模块。
    在这里插入图片描述

    一、Spring AOP 简介

    Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架的一个重要模块,用于提供声明式的事务管理、日志记录、性能监控等功能。Spring AOP 底层依赖于 AspectJ 实现,可以与 Spring 框架无缝集成,提供一种更加简单、直观的方式来处理企业应用中的常见问题。

    二、Spring AOP 原理

    1. 代理机制
      Spring AOP 采用代理机制实现,可以分为 JDK 动态代理和 CGLIB 动态代理。JDK 动态代理是通过实现目标类的接口,生成目标类的代理对象;CGLIB 动态代理是通过继承目标类,生成目标类的子类作为代理对象。
    2. 通知(Advice)
      通知是 Spring AOP 中实现切面功能的核心,可以分为五种类型:Before、After、AfterReturning、AfterThrowing 和 Around。通知的作用是在目标方法执行前、后或者抛出异常时执行特定的逻辑,实现对目标方法的增强。
    3. 切入点(Pointcut)
      切入点是 Spring AOP 中定义的一个表达式,用于指定哪些方法需要被增强。切点表达式可以使用 AspectJ 语言来编写,非常灵活。通过定义切入点,可以精确地控制哪些方法需要被增强。
    4. 切面(Aspect)
      切面是 Spring AOP 中的一种组件,包含切点和通知。切面可以将通用的逻辑(如日志、事务管理等)封装在一起,便于管理和维护。在 Spring AOP 中,可以通过 XML 配置文件或者 Java 代码来定义切面。
    5. 自动代理
      Spring AOP 框架支持自动代理,可以在运行时自动为指定类生成代理对象。自动代理的核心是 Spring AOP 容器,负责管理代理对象、切面和通知。通过自动代理,可以简化开发者的操作,提高开发效率。

    三、Spring AOP 案例分析

    以下通过一个简单的案例来演示 Spring AOP 的使用。

    1. 配置文件
      首先,创建一个配置文件 applicationContext.xml,用于定义目标类和切面类。
    <beans xmlns="http://www.springframework.org/schema/beans"  
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
          xmlns:aop="http://www.springframework.org/schema/aop"  
          xsi:schemaLocation="http://www.springframework.org/schema/beans  
          http://www.springframework.org/schema/beans/spring-beans.xsd  
          http://www.springframework.org/schema/aop  
          http://www.springframework.org/schema/aop/spring-aop.xsd">
         
       <bean id="target" class="com.example.TargetClass">bean>
         
       <bean id="aspect" class="com.example.AspectClass">bean>
         
       <aop:config proxy-target-class="true">  
             
           <aop:aspect ref="aspect">  
               <aop:before pointcut="execution(* com.example.TargetClass.*(..))" method="com.example.AspectClass.beforeAdvice">aop:before>  
               <aop:after pointcut="execution(* com.example.TargetClass.*(..))" method="com.example.AspectClass.afterAdvice">aop:after>  
           aop:aspect>  
       aop:config>  
    beans>  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    1. 目标类(TargetClass)
      目标类是一个简单的计算类,包含两个方法:doAdd 和 doSubtract。
    package com.example;
    public class TargetClass {  
       public int doAdd(int a, int b) {  
           System.out.println("TargetClass doAdd method called");  
           return a + b;  
       }
       public int doSubtract(int a, int b) {  
           System.out.println("TargetClass doSubtract method called");  
           return a - b;  
       }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 切面类(AspectClass)
      切面类包含两个通知方法:beforeAdvice 和 afterAdvice,分别用于在目标方法执行前和执行后执行特定逻辑。
    package com.example;
    import org.aspectj.lang.JoinPoint;  
    import org.aspectj.lang.annotation.Before;  
    import org.aspectj.lang.annotation.After;
    public class AspectClass {  
       @Before("execution(* com.example.TargetClass.*(..))")  
       public void beforeAdvice(JoinPoint joinPoint) {  
           System.out.println("Before advice: " + joinPoint.getSignature().getName());  
       }
       @After("execution(* com.example.TargetClass.*(..))")  
       public void afterAdvice(JoinPoint joinPoint) {  
           System.out.println("After advice: " + joinPoint.getSignature().getName());  
       }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 测试类(TestClass)
      测试类用于测试 Spring AOP 的效果。
    package com.example;
    import org.springframework.context.ApplicationContext;  
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    public class TestClass {  
       public static void main(String[] args) {  
           ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
           TargetClass target = (TargetClass) context.getBean("target");
           int result1 = target.doAdd(2, 3);  
           int result2 = target.doSubtract(5, 2);
           System.out.println("Result 1: " + result1);  
           System.out.println("Result 2: " + result2);  
       }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行测试类,输出结果如下:

    TargetClass doAdd method called  
    Before advice: doAdd  
    After advice: doAdd  
    Result 1: 5  
    TargetClass doSubtract method called  
    Before advice: doSubtract  
    After advice: doSubtract  
    Result 2: 3  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    从输出结果可以看出,在目标方法执行前和执行后分别执行了 beforeAdvice 和 afterAdvice 方法,说明 Spring AOP 已经成功实现了对目标方法的增强。

    四、Spring AOP 提供了两种动态代理方式

    JDK 动态代理和 CGLIB 动态代理。JDK 动态代理是基于接口实现的,而 CGLIB 动态代理是基于类实现的。这两种代理方式在性能上有一定的差别,JDK 动态代理更适合用于接口较多的场景,而 CGLIB 动态代理则更适合用于类较多的场景。

    4.1 下面是一个简单的 Spring AOP JDK 动态代理示例,演示了如何使用 Spring AOP 实现日志切面:

    1. 首先,创建一个切面类(Aspect),包含一个通知(Advice):
    package com.example.aspect;
    import org.aspectj.lang.JoinPoint;  
    import org.aspectj.lang.annotation.AfterReturning;  
    import org.aspectj.lang.annotation.Aspect;  
    import org.aspectj.lang.annotation.Before;  
    import org.aspectj.lang.annotation.Pointcut;  
    import org.springframework.stereotype.Component;
    @Aspect  
    @Component  
    public class LoggingAspect {
       @Pointcut("execution(* com.example.service.*.*(..))")  
       public void serviceMethods() {  
       }
       @Before("serviceMethods()")  
       public void logBefore(JoinPoint joinPoint) {  
           System.out.println("Before method: " + joinPoint.getSignature().getName());  
       }
       @AfterReturning(pointcut = "serviceMethods()", returning = "result")  
       public void logAfterReturning(JoinPoint joinPoint, Object result) {  
           System.out.println("After returning method: " + joinPoint.getSignature().getName());  
           System.out.println("Result: " + result);  
       }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    1. 接下来,创建一个目标类(Target Class),包含一个需要增强的方法:
    package com.example.service;
    import org.springframework.stereotype.Service;
    @Service  
    public class TargetService {
       public String sayHello(String name) {  
           System.out.println("Hello, " + name);  
           return "Hello, " + name;  
       }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 然后,创建一个 Spring 配置类,启用 AOP 支持,并扫描包含切面和目标类的包:
    package com.example;
    import org.springframework.context.annotation.ComponentScan;  
    import org.springframework.context.annotation.Configuration;  
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    @Configuration  
    @EnableAspectJAutoProxy  
    @ComponentScan(basePackages = {"com.example.aspect", "com.example.service"})  
    public class AppConfig {  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 最后,创建一个测试类,使用 Spring AOP 提供的 API 调用目标类的方法:
    package com.example;
    import org.springframework.context.ApplicationContext;  
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;  
    import org.springframework.stereotype.Component;
    @Component  
    public class Test {
       public static void main(String[] args) {  
           ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);  
           TargetService targetService = context.getBean(TargetService.class);  
           String result = targetService.sayHello("World");  
           System.out.println("Result: " + result);  
       }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行测试类,你将看到目标方法被切面增强的日志输出。这个示例展示了如何使用 Spring AOP JDK 动态代理实现简单的日志切面,以记录目标方法执行的前后状态。这有助于实现代码的重用和提高可维护性。

    4.2 下面是一个简单的 Spring AOP CGLIB 动态代理示例,演示了如何使用 Spring AOP 实现日志切面:

    1. 首先,创建一个切面类(Aspect),包含一个通知(Advice):
    package com.example.aspect;
    import org.aspectj.lang.JoinPoint;  
    import org.aspectj.lang.annotation.AfterReturning;  
    import org.aspectj.lang.annotation.Aspect;  
    import org.aspectj.lang.annotation.Pointcut;  
    import org.springframework.stereotype.Component;
    @Aspect  
    @Component  
    public class LoggingAspect {
       @Pointcut("execution(* com.example.service.*.*(..))")  
       public void serviceMethods() {  
       }
       @Before("serviceMethods()")  
       public void logBefore(JoinPoint joinPoint) {  
           System.out.println("Before method: " + joinPoint.getSignature().getName());  
       }
       @AfterReturning(pointcut = "serviceMethods()", returning = "result")  
       public void logAfterReturning(JoinPoint joinPoint, Object result) {  
           System.out.println("After returning method: " + joinPoint.getSignature().getName());  
           System.out.println("Result: " + result);  
       }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1. 接下来,创建一个目标类(Target Class),包含一个需要增强的方法:
    package com.example.service;
    import org.springframework.stereotype.Service;
    @Service  
    public class TargetService {
       public String sayHello(String name) {  
           System.out.println("Hello, " + name);  
           return "Hello, " + name;  
       }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 然后,创建一个 Spring 配置类,启用 AOP 支持,并扫描包含切面和目标类的包:
    package com.example;
    import org.springframework.context.annotation.ComponentScan;  
    import org.springframework.context.annotation.Configuration;  
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    @Configuration  
    @EnableAspectJAutoProxy  
    @ComponentScan(basePackages = {"com.example.aspect", "com.example.service"})  
    public class AppConfig {  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 最后,创建一个测试类,使用 Spring AOP 提供的 API 调用目标类的方法:
    package com.example;
    import org.springframework.context.ApplicationContext;  
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;  
    import org.springframework.stereotype.Component;
    @Component  
    public class Test {
       public static void main(String[] args) {  
           ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);  
           TargetService targetService = context.getBean(TargetService.class);  
           String result = targetService.sayHello("World");  
           System.out.println("Result: " + result);  
       }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行测试类,你将看到目标方法被切面增强的日志输出。这有助于实现代码的重用和提高可维护性。
    需要注意的是,CGLIB 动态代理需要 TargetService 类实现 equals() 和 hashCode() 方法,否则会报错。这是因为 CGLIB 需要生成目标类的代理类,而如果 TargetService 类没有实现 equals() 和 hashCode() 方法,那么生成的代理类将无法正确处理目标类的对象。

  • 相关阅读:
    网络安全深入学习第五课——热门框架漏洞(RCE— Apache Shiro 1.2.4反序列化漏洞)
    QRunnable与外界互传对象
    300分钟吃透分布式缓存-16讲:常用的缓存组件Redis是如何运行的?
    剑指 Offer 05. 替换空格 Java
    基于M5StickC Plus的可定时电子沙漏电子沙漏(LCD+软SPI+Arduino/C++)
    2022最全Java面试八股文,已经帮助512人进入大厂(备战明年春招必看)
    Linux基础知识——(1)基本指令
    CSP-J/S信息学奥赛-数据结构
    【无标题】
    玩转亚马逊 AWS IoT(3): SpringBoot 2.7 集成 AWS IoT 服务
  • 原文地址:https://blog.csdn.net/superdangbo/article/details/133819803