Spring AOP(1)

AOP概述

AOP是Spring框架的第二大核心(第一大核心是IoC).

什么是AOP?

Aspect Oriented Programming(面向切面编程)

什么是面向切面编程呢? 切面就是指某一类特定的问题, 所以AOP也可以叫做面向特定方法编程.

什么是面向特定方法编程呢?比如上一篇中讲到的拦截器, 就是对比如登录校验这一类问题的统一处理. 所以, 拦截器也算是AOP的一种应用. AOP是一种思想, 拦截器是AOP的一种实现. Spring框架实现了这种思想, 提供了拦截器技术的相关接口.

同样地, 统一数据返回格式和同意一场处理, 也是AOP思想的一种实现.

简单来说:AOP是一种思想, 是对某一类事情的集中处理.

什么是Spring AOP?

AOP是一种思想, 它的实现方法有很多, 有Spring AOP, 也有AspectJ, CGLIB等.

Spring AOP是其中的一种实现方式.

学会统一功能之后, 是不是就学会了AOP呢? 当然不是.

因为拦截器的作用维度是URL(一次请求和响应), @ControllerAdvice应用场景主要是全局异常处理(配合自定义异常效果更佳), 数据绑定, 数据预处理.AOP的作用维度更加细致(可以根据包, 类, 方法, 参数等进行拦截), 能够实现更加复杂的业务逻辑.

举个栗子:

我们现在有一个项目, 项目中开发了很多的业务功能:

现在有一些业务的执行业务比较低, 耗时比较长, 我们需要对接口进行优化.

第一步就需要定位出执行耗时比较长的业务方法, 再针对该业务方法来进行优化.

如何定位呢? 就需要统计当前项目中每一个业务方法的执行耗时.

如何统计呢? 可以在业务方法运行前和运行后, 记录下方法的开始时间和结束时间, 两者之差就是这个方法的耗时.

这种方法是可以解决问题的, 但一个项目中会包含很多业务模块, 每个业务模块又有很多接口, 一个接口又包含很多方法, 如果我们要在每个业务方法上都这么整, 哥们愚公都没你能搬.

AOP就可以做到在不改动这些原始方法的基础上, 针对特定的方法进行功能的加强.

AOP作用: 在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性:解耦)

Spring AOP快速入门

引入AOP依赖

在pom.xml文件中添加配置:

XML 复制代码
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

编写AOP程序

记录每个Controller每个方法的执行时间(细节后面会讲).

java 复制代码
@Slf4j
//需要被Spring管理
@Component
//spring使用了aspectJ的注解, 具体实现是Spring实现的. AspectJ是一个第三方的Jar包
@Aspect
public class TimeRecrodAspect {
    /**
     * 记录耗时
     */
    @Around("execution(* com.ashwakeup.demo.library.controller.*.*(..))")
    public Object timeRecord(ProceedingJoinPoint joinPoint) throws Throwable { //joinPoint可理解为目标方法
        //记录开始时间
        long start = System.currentTimeMillis();
        //执行目标方法
        Object proceed = joinPoint.proceed();
        //记录结束时间
        long end = System.currentTimeMillis();
        //日志打印耗时
        log.info("耗时时间: " + (end - start) + "ms");
        return proceed;
    }
}

运行程序, 观察日志:

对程序进行简单讲解:

1.@Aspect: 标识这是一个切面类.

2.@Around: 环绕通知, 在目标方法的前后都会执行. 后面的表达式表示对哪些方法进行增强.

3.ProceedingJoinPoint.proceed()让原始方法执行.

我们通过AOP入门程序完成了业务接口的执行耗时统计.

通过上面的程序, 我们也可以感受到AOP面向切面编程的优势:

代码无侵入: 主要是因为不修改原始的业务方法, 主要是对原始业务方法的功能进行增强.

减少代码重复

提高开发效率

维护方便

Spring AOP详解

Spring AOP核心概念

切点(Pointcut)

Pointcut的作用就是提供一组规则, 告诉程序对哪些方法的功能进行增强.

这里的: @Around("execution(* com.ashwakeup.demo.library.controller.*.*(..))")就是切点表达式.

连接点(Join Point)

满足切点表达式规则的所有方法, 就是连接点. 也就是可以被AOP控制的方法,

比如上图, 就是指所有在com.ashwakeup.demo.library.controller下的所有方法都是连接点.

切点和连接点的关系: 连接点是满足切点表达式的元素. 切点可以看作是保存了众多连接点的一个集合.

eg:

切点表达式: 一班学生

连接点:张三, 李四.......

通知(Advice)

通知就是指具体的工作, 指哪些重复的逻辑, 也就是共性功能(最终体现为一个方法).

比如上述程序中记录业务方法的耗时时间, 就是通知.

在AOP面向切面编程当中, 我们把这部分重复的代码逻辑抽取出来单独定义, 这部分代码就是通知的内容.

切面(Aspect)

切面(Aspect) = 切点(Pointcut) + 通知(Advice)

通过切面就能描述当前AOP程序需要针对哪些方法, 在什么时候执行什么样的工作.

切面既包含了通知逻辑的定义, 也包含了连接点的定义.

切面所在的类, 我们一般称为切面类(被@Aspect注解标识的类)

通知类型

上面讲了什么是通知, 接下来学习通知的类型. @Around就是其中一种通知类型, 表示环绕通知. Spring AOP的通知类型有以下几种:

@Around: 环绕通知, 此注解标注的通知方法在目标方法前后都会被执行

@Before: 前置通知, 此注解标注的通知方法在目标方法前被执行.

@After: 后置通知, 此注解标注的通知方法在目标方法后执行, 无论是否有异常都会被执行.

@AfterReturning: 返回后通知, 此注解标注的通知方法在目标方法后执行, 有异常不会执行.

@AfterThrowing: 异常后通知, 此注解标注的通知方法发生异常后执行.

接下来通过代码来加深对这几个方法的理解:

java 复制代码
@Slf4j
@Component
@Aspect
public class AspectDemo {
    //前置通知
    @Before("execution(* com.ashwakeup.demo.library.controller.TestController.*(..))")
    public void doBefore() {
        log.info("执行 Before 方法");
    }

    //后置通知
    @After("execution(* com.ashwakeup.demo.library.controller.TestController.*(..))")
    public void doAfter() {
        log.info("执行 After 方法");
    }

    //返回后通知
    @AfterReturning("execution(* com.ashwakeup.demo.library.controller.TestController.*(..))")
    public void doAfterReturning(){
        log.info("执行 AfterReturning 方法");
    }

    //抛出异常后通知
    @AfterThrowing("execution(* com.ashwakeup.demo.library.controller.TestController.*(..))")
    public void doAfterThrowing() {
        log.info("执行 AfterThrowing 方法");
    }

    //添加环绕通知
    @Around("execution(* com.ashwakeup.demo.library.controller.TestController.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Around方法开始时执行");
        Object result = joinPoint.proceed();
        log.info("Around方法结束后执行");
        return result;
    }
}

写一个测试程序:

java 复制代码
@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public String t1() {
        return "t1";
    }

    @RequestMapping("/t2")
    public boolean t2() {
        int a = 10 / 0;
        return true;
    }
}

运行代码, 观察日志:

1.执行t1的情况:

程序正常运行的情况下, @AfterThrowing标识的通知方法并不会执行.

从上图可以看出: 执行逻辑大致为: Around上半-> Before -> AfterReturning -> After -> Around下半.

2.异常时的情况:

程序发生异常的情况下:

@AfterReturning标识的通知方法不会执行, @AfterThrowing标识的通知方法执行了.

@Around环绕通知中原始方法调用时有异常, 通知中的环绕后的代码逻辑也不会在执行了(因为原始方法调用出异常了).

执行逻辑: @Around上半 -> @Before -> @AfterThrowing -> @After

注意事项:

@Around环绕通知需要调用ProceedingJoinPoint.proceed()来让原始方法执行, 其它通知不需要考虑目标执行.

@Around环绕通知方法的返回值, 必须指定为Object, 来接收原始方法的返回值, 否则原始方法执行完毕, 是获取不到返回值的.

一个切面类有多个切点.

相关推荐
憨子周30 分钟前
2M的带宽怎么怎么设置tcp滑动窗口以及连接池
java·网络·网络协议·tcp/ip
霖雨2 小时前
使用Visual Studio Code 快速新建Net项目
java·ide·windows·vscode·编辑器
SRY122404192 小时前
javaSE面试题
java·开发语言·面试
Fiercezm2 小时前
JUC学习
java
无尽的大道3 小时前
Java 泛型详解:参数化类型的强大之处
java·开发语言
ZIM学编程3 小时前
Java基础Day-Sixteen
java·开发语言·windows
我不是星海3 小时前
1.集合体系补充(1)
java·数据结构
内核程序员kevin3 小时前
在Linux环境下使用Docker打包和发布.NET程序并配合MySQL部署
linux·mysql·docker·.net
P.H. Infinity3 小时前
【RabbitMQ】07-业务幂等处理
java·rabbitmq·java-rabbitmq
爱吃土豆的程序员3 小时前
java XMLStreamConstants.CDATA 无法识别 <![CDATA[]]>
xml·java·cdata