Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Spring AOP】@Aspect结合案例详解(一): @Pointcut使用@annotation + 五种通知Advice注解

【Spring AOP】@Aspect结合案例详解(一): @Pointcut使用@annotation + 五种通知Advice注解

作者头像
天罡gg
发布于 2023-01-08 01:19:43
发布于 2023-01-08 01:19:43
9.2K011
代码可运行
举报
文章被收录于专栏:天罡gg天罡gg
运行总次数:11
代码可运行

文章目录


前言

在微服务流行的当下,在使用SpringCloud/Springboot框架开发中,AOP使用的非常广泛,尤其是@Aspect注解方式当属最流行的,不止功能强大,性能也很优秀,还很舒心!所以本系列就结合案例详细介绍@Aspect方式的切面的各种用法,力求覆盖日常开发中的各种场景。本文带来的案例是:打印Log,主要介绍@Pointcut切点表达式的@annotation方式,以及 五种通知Advice注解@Before、@After、@AfterRunning、@AfterThrowing、@Around

AOP与Spring AOP

在正式开始之前,我们还是先了解一下AOP与Spring AOP~ 在软件开发过程中,有一些逻辑横向遍布在各个业务模块中,像权限、监控、日志、事务、异常重试等等,所以造成代码分散且冗余度高,和业务代码混夹在一起, 写起来不够优雅,改起来更是一种折磨!为了解决这些问题,AOP(Aspect Oriented Programming:面向切面编程)也就应运而生了,它是一种编程思想,就像OOP(面向对象编程)也是一种编程思想,所以AOP不是某种语言或某个框架特有的,它实现的是将横向逻辑与业务逻辑解耦,实现对业务代码无侵入,从而让我们更专注于业务逻辑本身,本质是在不改变原有业务逻辑的情况下增强横切逻辑

在Spring中,AOP共有3种实现方式

  • Spring1.2 基于接口的配置:Spring最早的AOP实现是完全基于接口,虽然兼容,但已经不推荐了.
  • Spring2.0+ schema-based 配置 :Spring2.0之后,提供了 schema-based 配置,也就是xml的方式配置.
  • Spring2.0+ @Aspect配置:Spring2.0之后,也提供了 @Aspect 基于注解的实现方式,也就是本文的主角,也是目前最方便、最广泛使用的方式!(推荐)

@Aspect简单案例快速入门

@Aspect注解方式,它的概念像@Aspect、@Pointcut、@Before、@After、@Around等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的,主要有两大核心

  • 定义[切入点]:使用 @Pointcut 切点表达式,你可以理解成类似于正则表达式的强大东东。(本文先只介绍@annotation方式)
  • 定义[切入时机] 和 [增强处理逻辑]五种通知Advice注解 对[切入点]执行增强处理, 包括:@Before、@After、@AfterRunning、@AfterThrowing、@Around

如果没有AOP基础,对于概念可能会比较懵,所以先上一个最简单案例,基于@Aspect注解方式如何实现切面:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// @Aspect和@Component定义一个切面类
@Aspect
@Component
public class MethodLogAspect {
    // 核心一:定义切点(使用@annotation方式)
    @Pointcut(value = "@annotation(com.tiangang.aop.MethodLog)")
    public void pointCut() {

    }
    // 核心二:对切点增强处理(这是5种通知中的前置通知)
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置通知:" + joinPoint);
    }
}

一共没有几行代码,就非常简单实现在方法执行前打印日志的功能注解类如下(对于打上这个注解的方法 都会被切面类增强处理):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodLog {

}

pom.xml依赖:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

ok,接下来我们分别具体来看这两大核心**@PointcutAdvice**.


一、@Pointcut

@Pointcut切点表达式非常丰富,可以将 方法(method)、类(class)、接口(interface)、包(package) 等作为切入点,非常灵活,常用的有@annotation、@within、execution等方式,由于篇幅原因,本文先只介绍@annotation方式。

@annotation

@annotation方式是指:切入点 是指定作用于方法上的注解,即被Spring扫描到方法上带有该注解 就会执行切面通知。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Pointcut(value = "@annotation(com.tiangang.aop.MethodLog)")
public void pointCut() {

}

案例给出的@Pointcut说明: 语法:@Pointcut(value = "@annotation(注解类名)")

注:只有注解类名是动态的,其它是固定写法.


二、五种通知Advice

通过@Pointcut定义的切点,共有五种通知Advice方式:

注解

说明

@Before

前置通知,在被切的方法执行前执行

@After

后置通知,在被切的方法执行后执行,比return更后

@AfterRunning

返回通知,在被切的方法return后执行

@AfterThrowing

异常通知,在被切的方法抛异常时执行

@Around

环绕通知,这是功能最强大的Advice,可以自定义执行顺序

执行顺序如下:

我这里在Service里定义了一个除法方法divide(),在这个方法也打上@MethodLog注解,让它可以被切面横切。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service
public class DemoService {
    @MethodLog
    public Integer divide(Integer a, Integer b) {
        System.out.printf("方法内打印: a=%d  b=%d %n", a, b);
        return a / b;
    }
}

用于测试的controller代码,都很简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RestController
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private DemoService demoService;

    @GetMapping("/divide")
    public Integer divide(Integer a, Integer b) {
        return demoService.divide(a, b);
    }
}

1. @Before前置通知

前置通知在被切的方法执行之前执行!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Before("pointCut()")
public void before(JoinPoint joinPoint) throws NoSuchMethodException {
    printMethod(joinPoint, "[前置通知before]");
}

注解语法@Before("切点方法名()")

注:只有《切点方法名》是动态的,其它是固定写法.

方法语法:public void 方法名(JoinPoint joinPoint)

这里有个非常重要参数JoinPoint:连接点 。因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法. 里面有三个常用的方法:

getSignature()获取签名:

MethodSignature signature = (MethodSignature) joinPoint.getSignature();

通过signature可以获取名称 getName() 和 参数类型 getParameterTypes()

getTarget()获取目标类: Class<?> clazz = joinPoint.getTarget().getClass();

如果被切的类 是 被别的切面切过的类,可以使用AopUtils.getTargetClass获取一个数组,再从数组中找你期望的类。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import org.springframework.aop.support.AopUtils;
Class<?>[] targets = AopUtils.getTargetClass(joinPoint.getTarget()).getInterfaces();

getArgs()获取入参值

Object[] args = joinPoint.getArgs()

基于这3个方法,可以轻松打印:被切的类名、方法名、方法参数值、方法参数类型等,printMethod方法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void printMethod(JoinPoint joinPoint, String name) throws NoSuchMethodException {
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Class<?> clazz = joinPoint.getTarget().getClass();
    Method method = clazz.getMethod(signature.getName(), signature.getParameterTypes());
    System.out.printf("[MethodLogAspect]切面 %s 打印 -> [className]:%s  ->  [methodName]:%s  ->  [methodArgs]:%s%n", name, clazz.getName(), method.getName(), Arrays.toString(joinPoint.getArgs()));
}

调用测试类,输出结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[MethodLogAspect]切面 [前置通知before] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
方法内打印: a=10  b=2 

2. @After后置通知

后置通知在被切的方法执行之后执行,无论被切方法是否异常都会执行!

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@After("pointCut()")
public void after(JoinPoint joinPoint) throws NoSuchMethodException {
    printMethod(joinPoint, "[后置通知after]");
}

注解语法@After("切点方法名()")

注:只有《切点方法名》是动态的,其它是固定写法.

方法语法:public void 方法名(JoinPoint joinPoint)

调用测试类,输出结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[MethodLogAspect]切面 [前置通知after] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
方法内打印: a=10  b=2 
[MethodLogAspect]切面 [后置通知after] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]

3. @AfterRunning返回通知

返回通知在被切的方法return后执行,带有返回值,如果被切方法异常则不会执行!

这里多了一个参数Object result,注解上也多了一个参数:returning

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) throws NoSuchMethodException {
    printMethod(joinPoint, "[返回通知afterReturning]");
    System.out.printf("[MethodLogAspect]切面 [返回通知afterReturning] 打印结果 -> result:%s%n", result);
}

注解语法@AfterReturning(value = "切点方法名(), returning = "返回值参数名")

注:只有《切点方法名》和 《返回值参数名》是动态的,其它是固定写法.

方法语法:public void 方法名(JoinPoint joinPoint, Object result)

调用测试类,输出结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[MethodLogAspect]切面 [前置通知before] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
方法内打印: a=10  b=2 
[MethodLogAspect]切面 [返回通知afterReturning] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
[MethodLogAspect]切面 [返回通知afterReturning] 打印结果 -> result:5
[MethodLogAspect]切面 [后置通知after] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]

4. @AfterThrowing异常通知

异常通知只在被切方法异常时执行,否则不执行。

这里多了一个参数Exception e,表示捕获所有异常,也可以设置为具体某一个异常,例如NullPointerException、RpcException等等。注解上也多了一个参数:throwing

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@AfterThrowing(value = "pointCut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) throws NoSuchMethodException {
    printMethod(joinPoint, "[异常通知afterThrowing]");
    System.out.printf("[MethodLogAspect]切面 [异常通知afterThrowing] 打印异常 -> Exception:%s%n", e);
}

注解语法@AfterThrowing(value = "切点方法名(), throwing = "异常参数名")

注:只有《切点方法名》和 《异常参数名》是动态的,其它是固定写法.

方法语法:public void 方法名(JoinPoint joinPoint, Exception e)

调用测试类,输出结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[MethodLogAspect]切面 [前置通知before] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 0]
方法内打印: a=10  b=0 
[MethodLogAspect]切面 [异常通知afterThrowing] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 0]
[MethodLogAspect]切面 [异常通知afterThrowing] 打印异常 -> Exception:java.lang.ArithmeticException: / by zero
[MethodLogAspect]切面 [后置通知after] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 0]
2023-01-06 21:05:06.536 ERROR 15436 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause

5. @Around环绕通知

环绕通知方法可以包含上面四种通知方法,是最全面最灵活的通知方法。

这里的参数类型和其它通知方法不同,从JoinPoint 变为ProceedingJoinPoint

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    printMethod(joinPoint, "[环绕通知around][proceed之前]");
    // 执行方法, 可以对joinPoint.proceed()加try catch处理异常
    Object result = joinPoint.proceed();
    System.out.printf("[MethodLogAspect]切面 [环绕通知around][proceed之后]打印 -> [result]:%s%n", result);
    return result;
}

注解语法@Around("切点方法名()")

注:只有《切点方法名》是动态的,其它是固定写法.

方法语法:public Object 方法名(ProceedingJoinPoint joinPoint) throws Throwable

调用测试类,输出结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[MethodLogAspect]切面 [环绕通知around][proceed之前] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
[MethodLogAspect]切面 [前置通知before] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
方法内打印: a=10  b=2 
[MethodLogAspect]切面 [返回通知afterReturning] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
[MethodLogAspect]切面 [返回通知afterReturning] 打印结果 -> result:5
[MethodLogAspect]切面 [后置通知after] 打印 -> [className]:com.tiangang.service.DemoService  ->  [methodName]:divide  ->  [methodArgs]:[10, 2]
[MethodLogAspect]切面 [环绕通知around][proceed之后]打印 -> [result]:5

总结

本文主要说明了,如何通过@Aspect定义一个切面类,并结合打印Log案例主要介绍了两大核心的用法:

  • @Pointcut使用 @annotation 方式定义切入点
  • 五种通知(Advice)注解用法:@Before、@After、@AfterRunning、@AfterThrowing、@Around

如果感觉不错,欢迎关注我 天罡gg 分享更多干货: https://blog.csdn.net/scm_2008

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-01-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Spring AOP】@Aspect结合案例详解(二): @Pointcut使用@within和within(已附源码)
在微服务流行的当下,在使用Spring Cloud / Spring Boot框架开发中,AOP使用的非常广泛,尤其是@Aspect注解方式当属最流行的,不止功能强大,性能也很优秀,还很舒心!所以本系列就结合案例详细介绍@Aspect方式的切面的各种用法,力求覆盖日常开发中的各种场景。 本文主要介绍@Pointcut切点表达式的 @within和within 这两种切点指示符,结合案例,十分钟让你彻底搞懂!
天罡gg
2023/01/31
1.3K0
Spring高手之路19——Spring AOP注解指南
在现代软件开发中,面向切面编程(AOP)是一种强大的编程范式,允许开发者跨越应用程序的多个部分定义横切关注点(如日志记录、事务管理等)。本文将介绍如何在Spring框架中通过AspectJ注解以及对应的XML配置来实现AOP,在不改变主业务逻辑的情况下增强应用程序的功能。
砖业洋__
2024/06/16
6050
Spring高手之路19——Spring AOP注解指南
Spring注解式AOP面向切面编程.
1、AOP指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式。aop底层是动态代理。
别先生
2019/12/19
7420
Spring AOP 实践指南
Spring AOP(面向切面编程)是Spring框架的一个关键特性之一。它提供了一种在应用程序中实现横切关注点的方法,这些关注点通常会散布在应用程序的多个模块中,并且与核心业务逻辑存在交叉。
訾博ZiBo
2025/01/06
1940
Spring AOP 实践指南
Spring Boot2(六):使用Spring Boot整合AOP面向切面编程
众所周知,spring最核心的两个功能是aop和ioc,即面向切面和控制反转。本文会讲一讲SpringBoot如何使用AOP实现面向切面的过程原理。
鸟不拉屎
2019/07/04
11.4K0
【Spring进阶】基于注解的面向切面编程(AOP)详解
面向切面编程(AOP)是一种编程范式,它允许开发者将横切关注点(如日志记录、事务管理、安全性等)与业务逻辑分离,从而提高代码的模块化和可维护性。在Java中,AOP通常通过使用框架如Spring来实现。
王也518
2024/04/22
1.5K0
Spring官网阅读(十八)AOP的核心概念
在前面的文章中我们已经对IOC做过详细的介绍了,本文主要介绍AOP,关于其中的源码部分将在专门的源码专题介绍,本文主要涉及的是AOP的基本概念以及如何使用,本文主要涉及到官网中的第5、6两大章 ”
程序员DMZ
2020/07/07
8310
@Aspect 5种通知详解 | Spring系列第35篇
这几种通知用起来都比较简单,都是通过注解的方式,将这些注解标注在@Aspect类的方法上,这些方法就会对目标方法进行拦截,下面我们一个个来看一下。
路人甲Java
2020/07/14
3.9K0
@Aspect 5种通知详解 | Spring系列第35篇
3. AOP
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——​解耦。​调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
捞月亮的小北
2023/12/01
1980
3. AOP
Spring AOP使用入门案例
首先从start.spring.io上下载一个Spring Boot工程,只需要引入web依赖即可。然后我们创建一个Controller:
conanma
2021/10/28
7630
轻松上手Spring AOP,掌握切面编程的核心技巧
Spring框架是我们使用比较多的一个框架,而AOP又是Spring的核心特性之一,本篇文章将介绍一下AOP的切点表达式、通知等特性及如何使用Spring AOP。
索码理
2024/04/15
3570
轻松上手Spring AOP,掌握切面编程的核心技巧
Java开发框架之Spring AOP知识总结
AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充,它的主要编程对象是切面(aspect), 而切面模块化横切关注点.在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里。
用户1289394
2018/12/29
6160
Java开发框架之Spring AOP知识总结
Spring
Spring 是一个开源的轻量级 Java 开发框架,旨在简化企业级应用开发。它通过控制反转(IoC)和面向切面编程(AOP)实现组件解耦和横切逻辑分离,提升系统的可维护性与扩展性。Spring 提供统一的事务管理、灵活的 Web 开发框架(Spring MVC),并支持与多种第三方技术集成。其生态系统包括 Spring Boot(快速构建应用)、Spring Cloud(微服务架构支持)等,已成为现代 Java 开发的核心技术之一
用户11637817
2025/04/30
2770
【Spring Boot】032-配置AOP
在下面的参考文章中,AOP已经介绍得比较详细了,但是由于本人不常用(从来没用过) AOP ,所以算是非常生疏,因此借此契机再次了解一遍 AOP,
訾博ZiBo
2025/01/06
1880
Spring AOP:面向切面编程的利器
总之,Spring AOP是一种非常强大的编程技术,它可以帮助我们实现代码的解耦和复用,提高代码的可维护性和可扩展性。
小小程序员
2023/03/19
4020
Spring AOP:面向切面编程的利器
spring的AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
逍遥壮士
2020/09/18
4690
spring的AOP
Spring AOP小记
一、概述 在通常的开发过程中,我们调用的顺序通常是controller->service-dao,其中,service中包含着太多的业务逻辑,并且还要不断调用dao来实现自身的业务逻辑,经常会导致业务
Zephery
2018/03/12
7630
Spring AOP小记
注解实现spring AOP 示例
注解实现spring AOP 示例 package com.atguigu.spring.aop; import java.util.Arrays; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.sprin
yawn
2018/03/14
7630
SpringBoot Aspect 切面编程
Spring Boot中的Aspect是用于实现面向切面编程(Aspect-Oriented Programming,AOP)的一种机制。AOP是一种编程范式,通过将横切关注点(如日志记录、性能统计、事务管理等)从业务逻辑中分离出来,以模块化的方式进行处理。
高老师
2023/09/27
7160
Spring 详解(二)------- AOP关键概念以及两种实现方式
当我们为系统做参数验证,登录权限验证或者日志操作等,为了实现代码复用,我们可能把日志处理抽离成一个新的方法。但是这样我们仍然必须手动插入这些方法,这样的话模块之间高耦合,不利于后期的维护和功能的扩展,有了 AOP 我们可以将功能抽成一个切面,代码复用好,低耦合。
海向
2019/09/23
6860
Spring 详解(二)------- AOP关键概念以及两种实现方式
相关推荐
【Spring AOP】@Aspect结合案例详解(二): @Pointcut使用@within和within(已附源码)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档