spring AOP详解

文章目录

    • AOP
      • [1 环境准备](#1 环境准备)
        • [1.1 工程及接口创建](#1.1 工程及接口创建)
        • [1.2 工程存在的问题](#1.2 工程存在的问题)
          • [1.2.1 问题](#1.2.1 问题)
          • [1.2.2 解决思路](#1.2.2 解决思路)
      • [2 AOP面向切面编程](#2 AOP面向切面编程)
        • [2.1 AOP概述](#2.1 AOP概述)
        • [2.2 AOP原理分析](#2.2 AOP原理分析)
      • [3 基于注解的AOP](#3 基于注解的AOP)
        • [3.1 入门示例](#3.1 入门示例)
        • [3.2 使用流程](#3.2 使用流程)
        • [3.3 切入点表达式](#3.3 切入点表达式)
        • [3.4 练习](#3.4 练习)
        • [3.5 通知类型](#3.5 通知类型)

AOP

​ AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善;

​ 实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(例如,在对象运行时动态织入一些扩展功能或控制对象执行)。

1 环境准备

1.1 工程及接口创建
  • 第1步:创建普通 SpringBoot 工程 _07springaop

  • 第2步:创建计算器接口 Calculator ,并定义 加减乘除 4 个方法;

    java 复制代码
    package cn.tedu._07springaop.aop;
    
    public interface Calculator {
        int add(int m, int n);
        int sub(int m, int n);
        int mul(int m, int n);
        int div(int m, int n);
    }
  • 第3步:创建实现类 CalculatorImpl,并实现这 4 个方法,要带有日志功能;

    java 复制代码
    package cn.tedu._07springAop.aop;
    
    public class CalculatorImpl implements Calculator{
        @Override
        public int add(int m, int n) {
            System.out.println("[日志]add方法开始..." );
            int result = m + n;
            System.out.println("[日志]add方法结束...);
    
            return result;
        }
    
        @Override
        public int sub(int m, int n) {
            System.out.println("[日志]sub方法开始... );
            int result = m - n;
            System.out.println("[日志]sub方法结束...);
            return result;
        }
    
        @Override
        public int mul(int m, int n) {
            System.out.println("[日志]mul方法开始... );
            int result = m * n;
            System.out.println("[日志]mul方法结束...);
            return result;
        }
    
        @Override
        public int div(int m, int n) {
            System.out.println("[日志]div方法开始...");
            int result = m / n;
            System.out.println("[日志]div方法结束...");
            return result;
        }
    }
1.2 工程存在的问题
1.2.1 问题

​ 附加功能分散在各个业务功能方法中,不利于统一维护

1.2.2 解决思路
  • 解耦:将附加功能代码从业务功能代码中抽取出来;
  • 难点:要抽取的代码在方法内部,使用之前技术不太好解决,而AOP技术可以解决这个难题。

2 AOP面向切面编程

2.1 AOP概述

​ AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善;

​ 在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2.2 AOP原理分析
  • 问题思考

    假如现在有一个业务对象,这个对象已经实现了一些核心业务功能,但是我们希望在核心业务的基础上在添加一些拓展业务,而且要求不能对目标业务对象中的实现进行修改(遵循OCP原则-对扩展开放,对修改关闭),请问如何实现?

  • 解决思路

    为目标类创建子类或者为目标类创建兄弟类,对目标业务进行功能拓展。这种方式可以实现,但如果需要进行业务拓展的类有很多,我们每个类都要基于目标类型进行子类或兄弟类的创建,工作量会比较大;

    AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象.如图:

    其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种(先了解):

    • 第一种方式:借助JDK官方API(Proxy)为目标对象类型创建其兄弟类型对象;

    • 第二种方式:借助CGLIB库为目标对象类型创建其子类类型对象.

3 基于注解的AOP

​ 基于注解的AOP是一种AOP的实现方式,它通过在Java类、方法、参数等上添加注解的方式来实现切面的定义和应用,相比于传统的XML配置方式更加便捷和灵活.

3.1 入门示例
  • 第1步:添加依赖

    xml 复制代码
    <!--引入aop依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
  • 第2步:准备被代理的目标资源

    接口

    java 复制代码
    package cn.tedu.aop;
    
    public interface Calculator {
        int add(int m, int n);
        int sub(int m, int n);
        int mul(int m, int n);
        int div(int m, int n);
    }

    实现类

    java 复制代码
    package cn.tedu._07springAop.aop;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class CalculatorImpl implements Calculator{
        @Override
        public int add(int m, int n) {
            int result = m + n;
            System.out.println("方法内部:" + result);
            return result;
        }
    
        @Override
        public int sub(int m, int n) {
            int result = m - n;
            System.out.println("方法内部:" + result);
            return result;
        }
    
        @Override
        public int mul(int m, int n) {
            int result = m * n;
            System.out.println("方法内部:" + result);
            return result;
        }
    
        @Override
        public int div(int m, int n) {
            int result = m / n;
            System.out.println("方法内部:" + result);
            return result;
        }
    }
  • 第3步:创建切面类并配置 LogAspect

    用于进行切入点的定义,功能增强方法的定义,在方法内部做日志业务增强,关键代码如下:

    java 复制代码
    package cn.tedu._07springAop.aop;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    /**
     * Aspect注解:表示这个类是一个切面类
     */
    @Aspect
    @Component
    public class LogAspect {
        @Before("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")
        public void beforeMethod(JoinPoint joinPoint) {
            System.out.println("LogAspect类中:前置通知,方法开始记录日志了...");
        }
    }
  • 第4步:测试类中进行测试

    java 复制代码
    @SpringBootTest
    class ApplicationTests {
        @Test
        void contextLoads() {
            ApplicationContext context = new AnnotationConfigApplicationContext("cn.tedu._07springAop");
            Calculator calculator = context.getBean(Calculator.class);
            int addResult = calculator.add(10, 20);
        }
    }
  • 第5步:执行结果

3.2 使用流程
  • 第1步:添加 aop 依赖并刷新 Maven

    xml 复制代码
    <!--引入aop依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
  • 第2步:创建切面类(选择通知的方式);

    注意添加 @Aspect 注解,表示该类为一个切面类.

3.3 切入点表达式

​ 在 AOP 中,切入点表达式指定了哪些方法需要被植入增强逻辑 。它是一个表达式,用于匹配目标对象中的方法,并提供切入点的精确定义

  • 切入点表达式语法
    • 权限修饰符:* 表示权限修饰符不限;
    • 方法返回值:* 表示返回值不限;
    • 包名部分: *... 表示包名任意,包的层次和深度任意;
    • 类名部分: * 表示类名任意;
    • 方法名部分:* 表示方法名任意;
    • 方法参数列表部分:(...) 表示参数列表任意;
3.4 练习
  • 项目背景

    假设你有一个OrderService接口,其中有一个方法placeOrder,无返回值。这个方法在执行时,需要先进行订单数量的检查,如果订单数量大于0,则继续执行订单创建,否则返回异常 信息,具体异常为:IllegalArgumentException

  • 操作步骤

    • 第1步:创建接口OrderService和接口方法 placeOrder[需要有一个int类型的参数orderNumber];
    • 第2步:创建实现类 OrderServiceImpl,实现该方法,打印:订单创建成功;
    • 第3步:创建切面类 OrderCheckAspect ,前置通知 beforeMethod,进行订单数量的校验
    • 第4步:创建测试类进行测试
  • 要求

    • 请使用AOP来实现上述功能;
    • 给出切面的定义和通知的定义。
3.5 通知类型
  • 前置通知:@Before 在被代理的目标方法执行

  • 返回通知:@AfterReturning 在被代理的目标方法成功结束后执行

  • 后置通知:@After 在被代理的目标方法最终结束后执行

  • 异常通知:@AfterThrowing 在被代理的目标方法异常结束后执行

  • 环绕通知:@Around 使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

问题:返回通知 和 后置通知 的区别是什么?

  • 执行时机

    返回通知是在目标方法执行后返回结果时执行的通知,只有目标方法正常返回时才会执行,抛异常则不会执行。后置通知则是在目标方法执行后执行的通知,无论目标方法是否抛出异常,后置通知都会执行。

  • 访问权限

    返回通知可以获取并修改目标方法的返回值,后置通知无法访问目标方法的返回值。

通知类型示例代码

java 复制代码
package cn.tedu._07springAop.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * Aspect注解:表示这个类是一个切面类
 */
@Aspect
@Component
public class LogAspect {
    @Before("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        /*
            getSignature(): 获取连接点签名信息;
            getName(): 获取连接点名称;
            getArgs(): 获取连接点参数;
         */
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("LogAspect类中:前置通知,方法开始记录日志了..." + methodName + ":" + args);
    }

    @After("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")
    public void aftherMethod(JoinPoint joinPoint){
        System.out.println("LogAspect类中:后置通知,方法开始记录日志了...");
    }

    @AfterReturning(value = "execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        System.out.println("LogAspect类中:返回通知,方法开始记录日志了...");
    }

    @AfterThrowing(value = "execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
        System.out.println("LogAspect类中:异常通知,方法开始记录日志了...");
    }

    @Around("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        Object result = null;
        try {
            System.out.println("环绕通知:目标对象方法执行之前");
            //目标对象方法的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知:目标对象方法执行之后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知:目标对象方法出现异常");
        } finally {
            System.out.println("环绕通知:目标对象方法执行完毕");
        }

        return result;
    }
}
相关推荐
初晴~5 分钟前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱5813610 分钟前
InnoDB 的页分裂和页合并
数据库·后端
小_太_阳31 分钟前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾33 分钟前
scala借阅图书保存记录(三)
开发语言·后端·scala
黑胡子大叔的小屋1 小时前
基于springboot的海洋知识服务平台的设计与实现
java·spring boot·毕业设计
ThisIsClark1 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
星就前端叭1 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
雷神乐乐2 小时前
Spring学习(一)——Sping-XML
java·学习·spring
小林coding2 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者2 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu