【Java EE进阶 --- SpringBoot】Spring 核心 --- AOP

🚀 欢迎来到我的CSDN博客:Optimistic _ chen

一名热爱技术与分享的全栈开发者,在这里记录成长,专注分享编程技术与实战经验,助力你的技术成长之路,与你共同进步!


🚀我的专栏推荐

专栏 内容特色 适合人群
🔥C语言从入门到精通 系统讲解基础语法、指针、内存管理、项目实战 零基础新手、考研党、复习
🔥Java基础语法 系统解释了基础语法、类与对象、继承 Java初学者
🔥Java核心技术 面向对象、集合框架、多线程、网络编程、新特性解析 有一定语法基础的开发者
🔥Java EE 进阶实战 Servlet、JSP、SpringBoot、MyBatis、项目案例拆解 想快速入门Java Web开发的同学
🔥Java数据结构与算法 图解数据结构、LeetCode刷题解析、大厂面试算法题 面试备战、算法爱好者、计算机专业学生

🚀我的承诺:

✅ 文章配套代码:每篇技术文章都提供完整的可运行代码示例

✅ 持续更新:专栏内容定期更新,紧跟技术趋势

✅ 答疑交流:欢迎在文章评论区留言讨论,我会及时回复(支持互粉)


🚀 关注我,解锁更多技术干货!
⏳ 每天进步一点点,未来惊艳所有人!✍️ 持续更新中,记得⭐收藏关注⭐不迷路 ✨

📌 标签:#技术博客#编程学习#Java#C语言#算法#程序员

文章目录

前言

前面学习过Spring的第一大核心Spring-Ioc,受到众多读者访问,而今天要了解的AOP甚至比它更加抽象,难以理解。

AOP思想

AOP全称:Aspect Oriented Programming(面向切面编程) ;切面:指某一类特定问题(后面会系统学习)。所以:AOP也可以理解为面向特定方法编程的思想。

核心思想:将业务逻辑与横切关注点分离,集中解决某一类问题,也就是上篇博客写的统一处理问题,就是AOP思想的具体实现。

Spring AOP:AOP是一种思想,而Spring框架实现了这种思想。

初学AOP

我们会通过一个例子来体验一下Spring AOP的使用:实现一个功能记录各个接口方法的执行时间。

正常情况下,我们首先考虑的就是在方法运行前和运行后,记录下开始时间和结束时间,计算二者之差即可。

java 复制代码
 long startTime=System.currentTimeMillis();
 //执行方法
 long endTime=System.currentTimeMillis();
 log.info("方法耗时:"+(endTime-startTime)+" ms");

这个方法可以,但是如果我在一个项目中要测超级多的接口方法耗时,那意味着我要去给每一个方法写这段代码,工作量太大。

那这时就需要AOP来解决:AOP在程序运行期间在不修改源代码的基础上对已有方法进行增强。

准备工作

引入AOP依赖:

在项目中引入Spring AOP的依赖,在pom.xml中添加:

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

编写记录每个方法耗时的程序:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import javax.naming.event.ObjectChangeListener;

@Slf4j
@Component
@Aspect
public class TimeAspect {
    /**
     * 记录方法耗时
     */
    @Around("execution(* com.zc.blog.controller.*.*(..))")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        
        long startTime=System.currentTimeMillis();
        Object result=pjp.proceed();
        long endTime=System.currentTimeMillis();
        
        log.info("方法耗时:"+(endTime-startTime)+" ms");
        return result;
    }
}


@Aspect:标识这是⼀个切⾯类
@Around: 环绕通知,在⽬标⽅法的前后都会被执⾏ . 后⾯的表达式表⽰对哪些⽅法进⾏增强
ProceedingJoinPoint.proceed() 让原始⽅法执⾏

总结:通过上面的实例,我们发现AOP编程的优点
1.代码解耦性强:无入侵性,在不修改原始代码的基础上就可以对原始方法进行增强
2.减少代码的重复率
3.维护方便

Spring AOP详解

切点(Pointcut)

切点,也称为"切入点":提供一组规则(切点表达式语言),告诉程序对哪些方法进行增强。也就是告诉程序被增强方法的路径

连接点(Join Point)

满足切点表达式规则的方法,就是连接点,也就是能被AOP控制的方法。

实例中,controller包中的所有方法都是连接点。

比如:

|--------------------------------------|
| 切点表达式:计算机学院所有学生。 连接点:张三 、小王、小赵、小段等学生 |

通知(Advice)

通知不是我们理解的"通知",这里指:具体要完成的工作,那些重复的逻辑,也就是功能增强部分 (记录耗时时间的方法)

在AOP面向切面编程中,这部分重复的逻辑代码单独定义,这部分代码就是通知的内容。

切面(Aspect)

切面(Aspect)=切点(Pointcut)+ 通知(Advice)
通过公式就知道,切面是通过切点完成通知;也是当前AOP程序针对哪些方法(切点),在什么时候执行什么操作(通知)

注意:切⾯所在的类,我们⼀般称为切⾯类(被@Aspect注解标识的类)

通知类型

Spring AOP提供了五种通知类型

  • 前置通知(@Before):在目标方法执行之前执行
  • 后置通知(@After):在目标方法执行之后执行,无论方法是否正常返回
  • 返回通知(@AfterReturning):在目标方法正常返回之后执行
  • 异常通知(@AfterThrowing):在目标方法抛出异常时执行
  • 环绕通知(@Around):围绕目标方法执行,可以在方法执行前后自定义逻辑

我们通过代码来显式的观察这几种类型:

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

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

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

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

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

程序运行逻辑:

方法抛出异常时,表示方法逻辑有误,通知中的环绕后的代码逻辑也不会执行,因为@Around环绕通知后需要调用joinPoint.proceed()方法。

注意:观察代码发现,一个切面类可以有多个切点

@PointCut

我们发现上面代码的切点全部相同,这样代码中存在大量重复,Spring提供的@PointCut注解可以把公共的切点提取出来,需要时引入该切点即可。(类似于数学中的乘法分配律)

java 复制代码
@Slf4j
@Component
@Aspect
public class AspectDemo {
    @Pointcut("execution(* com.zc.blog.controller.*.*(..))")
    private void pt(){};

    //前置通知
    @Before("pt()")
    public void doBefore(){
        log.info("执行Before方法");
    }

    //后置通知
    @After("pt()")
    public void doAfter(){
        log.info("执行After方法");
    }
    ...

同时目前切点定义只能在当前切面类中使用,如果在其他切面类中使用,就需要把private改为public ,引用方式为:全限定类名.方法名()

java 复制代码
@Before("com.zc.blog.Demo.AspectDemo.pt()")
public void doBefore(){
        log.info("执行Before方法");
    }

切面优先级@Order

当我们在项目中定义多个切面类时,并且这些切面类中有多个切点都匹配到同一个方法,当这个方法运行时,这些切面类中的通知都会运行,那么它们的运行顺序是什么呢?

多创建几个切面类测试:

发现规律:

  • @Before通知:排名靠前的先执行
  • @After通知:排名靠后的后执行

但是这种情况不受我们程序员掌控,为了提高我们对代码的控制,使用@Order注解:现在我们按照默认的倒序运行

java 复制代码
@Slf4j
@Component
@Aspect
@Order(1)
public class AspectDemo4 {
    @Pointcut("execution(* com.zc.blog.controller.*.*(..))")
    private void pt(){};
    
    //.....
}

@Slf4j
@Component
@Aspect
@Order(2)
public class AspectDemo3 {
    @Pointcut("execution(* com.zc.blog.controller.*.*(..))")
    private void pt(){};
    
    //.....
}


@Slf4j
@Component
@Aspect
@Order(3)
public class AspectDemo2 {
    @Pointcut("execution(* com.zc.blog.controller.*.*(..))")
    private void pt(){};
    
    //.....
}


使用@Order注解执行顺序是:

  • @Before通知:数字越小先执行
  • @After通知:数字越大后执行

总结:@Order控制切⾯的优先级,先执⾏优先级较⾼的切⾯,再执⾏优先级较低的切⾯,最终执⾏⽬标⽅法

切点表达式

常见的表达式有两种:

  1. execution(......): 根据方法的签名来匹配
  2. @annotation(...): 根据注解匹配

execution表达式

语法规则:

execution(<访问修饰符> <返回类型> <包名.类名.⽅法(⽅法参数)> <异常>)

@annotation

匹配多个类的方法,execution表达式实现比较复杂,我们借助自定义注解的方式以及 切点表达式@annotation 来描述这个场景

编写自定义注解@MyAspect
java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}

简单解释一下两个注解:
@Target 标识了Annotation 所修饰的对象范围,即该注解可以⽤在什么地⽅

ElementType.TYPE: ⽤于描述类、接⼝(包括注解类型)或enum声明
ElementType.METHOD :描述⽅法
ElementType.PARAMETER :描述参数
ElementType.TYPE_USE: 可以标注任意类型

@RetentionAnnotation被保留的时间长短,表示注解的生命周期

RetentionPolicy.RUNTIME运⾏时注解.表⽰注解存在于源代码,字节码和运⾏时中.这意味着在编译时,字节码中和实际运⾏时都可以通过反射获取到该注解的信息.通常⽤于⼀些需要在运⾏时处理的注解
RetentionPolicy.CLASS编译时注解.表⽰注解存在于源代码和字节码中,但在运⾏时会被丢弃.这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运⾏时⽆法获取.通常⽤于⼀些框架和⼯具的注解
RetentionPolicy.SOURCE :表⽰注解仅存在于源代码中,编译成字节码后会被丢弃.这意味着在运⾏时⽆法获取到该注解的信息,只能在编译时使⽤

使用@annotation表达式

使用@annotation表达式定义切点时,只对@MyAspect生效

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

}

完结撒花!🎉

如果这篇博客对你有帮助,不妨点个赞支持一下吧!👍
你的鼓励是我创作的最大动力~

想获取更多干货? 欢迎关注我的专栏 → optimistic_chen

📌 收藏本文,下次需要时不迷路!

我们下期再见!💫 持续更新中......


悄悄说:点击主页有更多精彩内容哦~ 😊

相关推荐
asom222 小时前
互联网大厂Java求职面试实战:Spring Boot到Kubernetes的技术问答
java·spring boot·kubernetes·oauth2·电商·microservices·面试技巧
虎子_layor2 小时前
轻量级哈希扰动工具:Hashids,快速上手
java·spring
卡提西亚2 小时前
一本通网站1130:找第一个只出现一次的字符
数据结构·c++·笔记·算法·一本通
烽学长2 小时前
(附源码)基于Spring boot的校园志愿服务管理系统的设计与实现
java·spring boot·后端
失散132 小时前
分布式专题——49 SpringBoot整合ElasticSearch8.x实战
java·spring boot·分布式·elasticsearch·架构
蒙奇D索大3 小时前
【算法】递归算法的深度实践:深度优先搜索(DFS)从原理到LeetCode实战
c语言·笔记·学习·算法·leetcode·深度优先
chinesegf3 小时前
[特殊字符] 常用 Maven 命令
java·spring boot·maven
杰克尼3 小时前
Springcloud_day01
spring boot·spring·mybatis
鸽鸽程序猿4 小时前
【项目】【抽奖系统】活动创建
java·spring