Springboot注解@Aspect

news/2025/3/18 14:28:08/文章来源:https://www.cnblogs.com/hanease/p/18778877

@Aspect的使用
配置
要启用 Spring AOP 和 @Aspect 注解,需要在 Spring 配置中启用 AspectJ 自动代理,但是在 Spring Boot 中,通常不需要显式地添加 @EnableAspectJAutoProxy,因为 Spring Boot 的自动配置特性已经包含了这一设置。:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
// ...
}
@Aspect 注解也要写@Component,或通过其他方式注册为 Spring Bean,以确保 Spring 容器能够识别并管理这个切面。

@Aspect
@Component
public class LoggingAspect {
}
作用
定义切面(Aspect):

切面是跨多个类或对象的横切关注点的模块化。在 Spring AOP 中,切面是通过使用 @Aspect 注解的类来表示的。
切入点(Pointcut):

使用 @Pointcut 注解来定义切入点表达式,指定切面应用的位置。切入点定义了切面应该在何处插入其横切逻辑,即切面应该应用的连接点(如方法执行)的集合。
通知(Advice):

前置通知(Before advice):在某连接点(方法执行等)之前执行,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
后置通知(After advice):在某连接点之后执行,无论该连接点是正常完成还是抛出异常。
返回后通知(After-returning advice):在某连接点正常完成后执行。

抛出异常后通知(After-throwing advice):如果方法抛出异常退出,则执行通知。

环绕通知(Around advice):在方法调用之前和之后执行,它将决定是否继续执行连接点或直接返回自己的返回值或抛出异常。

通知相关的注解
@Around:环绕增强: 就是既可以前置增强,也可以后置增强。环绕通知会影响到AfterThrowing通知的运行,不要同时使用。
@Before:标识一个前置增强方法,

@AfterReturning:后置增强,如无返回结果,此注解不会执行

@After:final增强,不管是抛出异常或者正常退出都会执行

@AfterThrowing: 异常抛出增强

例子
假设我们有一个服务类 SampleService,我们想在其方法执行的不同阶段添加日志。

public class SampleService {
public void performAction() {
System.out.println("Performing action in SampleService");
}
}
现在,我们定义一个切面 LoggingAspect 来添加日志:

@Aspect
@Component
public class LoggingAspect {

// 前置通知:在方法执行之前执行
@Before("execution(* SampleService.performAction(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before executing: " + joinPoint.getSignature().getName());
}

// 后置通知:在方法执行之后执行(无论是否发生异常)
@After("execution(* SampleService.performAction(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After executing: " + joinPoint.getSignature().getName());
}

// 返回后通知:在方法成功执行之后执行
@AfterReturning("execution(* SampleService.performAction(..))")
public void logAfterReturning(JoinPoint joinPoint) {
System.out.println("After returning from: " + joinPoint.getSignature().getName());
}

// 异常后通知:在方法抛出异常后执行
@AfterThrowing(pointcut = "execution(* SampleService.performAction(..))", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
System.out.println("After throwing in: " + joinPoint.getSignature().getName() + ", Exception: " + ex);
}

// 环绕通知:在方法执行之前和之后执行
@Around("execution(* SampleService.performAction(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around before: " + joinPoint.getSignature().getName());
Object result = joinPoint.proceed(); // 继续执行方法
System.out.println("Around after: " + joinPoint.getSignature().getName());
return result;
}
}
结果:
就@Before来说,只要performAction方法被调用,那么第一步就是先执行这段代码:System.out.println("Before executing: " + joinPoint.getSignature().getName());执行完后在执行performAction方法。其他方法也是如此只是触发时机不同罢了。

@Aspect作用和Spring Aop关系
@Aspect 是 AspectJ 框架中的一个注解,它是面向切面编程(AOP)的一个关键部分。AspectJ 是一个独立的 AOP 框架,而 Spring 框架(包括 Spring Boot)则集成了 AspectJ 的一部分功能,使得开发者可以在 Spring 应用中方便地使用 AOP。

AOP 是一种编程范式(软件工程中的一类典型的编程风格。),用于增加程序的模块化,通过将横切关注点(如日志、安全、事务管理等)从业务逻辑中分离出来,提高代码的可重用性和可维护性。说白了其实我感觉就是动态代理,可以为相关方法前后等统一进行一些处理。

Spring Boot 作为 Spring 框架的一个扩展,继承了 Spring 的 AOP 功能。在 Spring Boot 应用中,AOP 使用代理模式来实现 AOP,其中 @Aspect 注解的类被当作一个带有通知(Advice)和切入点(Pointcut)的切面。

Spring AOP 使用代理模式来拦截对对象的方法调用。当在 Spring 应用中定义了 AOP 切面时,Spring 容器会为匹配切面指定的切入点的 Bean 创建一个代理对象。这个代理对象在调用原始方法之前或之后执行切面中定义的逻辑。

动态代理:如果一个 Bean 至少实现了一个接口,Spring AOP 默认会使用 JDK 的动态代理来创建这个 Bean 的代理。
CGLIB 代理:如果一个 Bean 没有实现任何接口,Spring AOP 会使用 CGLIB 库来创建代理。
虽然使用 @Aspect 注解和 @Before、@After 等注解定义了切面的行为,但这些注解本身并不负责拦截方法调用。真正的方法拦截是由 Spring AOP 在背后通过动态代理机制来实现的。当方法被调用时,实际上是先调用代理对象的对应方法,代理对象再根据 AOP 配置来决定是否调用原始对象的方法,以及在调用前后执行哪些额外的逻辑。

示例
假设有一个简单的服务类 MyService,我们想在其 performAction 方法执行前后添加日志:

public class MyService {
public void performAction() {
System.out.println("Performing action");
}
}

@Aspect
@Component
public class LoggingAspect {
@Before("execution(* MyService.performAction(..))")
public void logBefore() {
System.out.println("Before action");
}

@After("execution(* MyService.performAction(..))")
public void logAfter() {
System.out.println("After action");
}
}

在这里,LoggingAspect 定义了在 MyService 的 performAction 方法执行前后要执行的日志操作。但实际上,当 performAction 方法被调用时,它是通过 MyService 的代理来调用的,代理负责根据 LoggingAspect 的配置执行相应的 AOP 逻辑。

发现通知参数中的joinPoint不知道做什么用,请看我的下一章 Springboot注解@Aspect(二)JoinPoint 使用详解

标签表达式
上述例子中你会好奇 @Before("execution(* SampleService.performAction(..))"),这中间的execution是什么意思后面括号内是什么意思。这其实是注解使用的标签表达式,有如下这些:

within:用于匹配指定类型内的方法执行

this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也* 类型匹配

target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配

args:用于匹配当前执行的方法传入的参数为指定类型的执行方法

@within:用于匹配所以持有指定注解类型内的方法

@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解,和@annotation区别是不用全路径

@args:用于匹配当前执行的方法传入的参数持有指定注解的执行

@annotation:用于匹配当前执行方法持有指定注解的方法

主要记住execution和@annotation用的比较多,一个是用在方法上一个是用在相关注解上

如何编写切入点表达式:

匹配特定方法:execution([权限加返回类型] [类路径].[方法名称]([参数]))

 

匹配带有特定注解的方法:@annotation(com.example.MyAnnotation)

匹配所有被 @MyAnnotation 注解的方法。
其中:

(..)代表所有参数

(*,String)代表第一个参数为任何值,第二个为String类型,

(..,String)代表最后一个参数是String类型
————————————————

JoinPoint 的作用
在 Spring AOP 中,JoinPoint 接口代表了一个程序执行的点,比如方法执行或异常处理。当使用 AOP 通知(Advice)时,你可以将 JoinPoint 作为参数传递到通知方法中,以便获取有关当前执行点的详细信息。

JoinPoint 提供了一种方式来访问当前被通知方法的详细信息,如方法签名、参数等。这在编写通知逻辑时非常有用,因为你可以根据当前执行的方法来修改通知的行为。

JoinPoint:

用于所有类型的通知(@Before、@After、@AfterReturning、@AfterThrowing),但不包括环绕通知。
JoinPoint 常用方法
getArgs():返回一个对象数组,包含了被通知方法的参数。

getThis():返回代理对象。

getTarget():返回目标对象。

getSignature():返回被通知方法的签名信息。

toString():打印出正在执行的被通知方法的详细信息。

toShortString():提供正在执行的被通知方法的简短描述。

toLongString():提供正在执行的被通知方法的完整描述

示例
假设你有一个前置通知(Before),你想在方法执行之前打印方法名称和参数:

ProceedingJoinPoint对象:ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中。

@Aspect
@Component
public class aopAspect {
/**
* 定义一个切入点表达式,用来确定哪些类需要代理
* execution(* aopdemo.*.*(..))代表aopdemo包下所有类的所有方法都会被代理
*/
@Pointcut("execution(* aopdemo.*.*(..))")
public void declareJoinPointerExpression() {}

/**
* 前置方法,在目标方法执行前执行
* @param joinPoint 封装了代理方法信息的对象,若用不到则可以忽略不写
*/
@Before("declareJoinPointerExpression()")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
//获取传入目标方法的参数
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println("第" + (i+1) + "个参数为:" + args[i]);
}
System.out.println("被代理的对象:" + joinPoint.getTarget());
System.out.println("代理对象自己:" + joinPoint.getThis());
}

/**
* 环绕方法,可自定义目标方法执行的时机
* @param pjd JoinPoint的子接口,添加了
* Object proceed() throws Throwable 执行目标方法
* Object proceed(Object[] var1) throws Throwable 传入的新的参数去执行目标方法
* 两个方法
* @return 此方法需要返回值,返回值视为目标方法的返回值
*/
@Around("declareJoinPointerExpression()")
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result = null;

try {
//前置通知
System.out.println("目标方法执行前...");
//执行目标方法
//result = pjd.proeed();
//用新的参数值执行目标方法
result = pjd.proceed(new Object[]{"newSpring","newAop"});
//返回通知
System.out.println("目标方法返回结果后...");
} catch (Throwable e) {
//异常通知
System.out.println("执行目标方法异常后...");
throw new RuntimeException(e);
}
//后置通知
System.out.println("目标方法执行后...");

return result;
}
}

被代理类

/**
* 被代理对象
*/
@Component
public class TargetClass {
/**
* 拼接两个字符串
*/
public String joint(String str1, String str2) {
return str1 + "+" + str2;
}
}
测试类

public class TestAop {
@Test
public void testAOP() {
//1、创建Spring的IOC的容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:bean.xml");

//2、从IOC容器中获取bean的实例
TargetClass targetClass = (TargetClass) ctx.getBean("targetClass");

//3、使用bean
String result = targetClass.joint("spring","aop");
System.out.println("result:" + result);
}
}
结果:

目标方法执行前...
目标方法名为:joint
目标方法所属类的简单类名:TargetClass
目标方法所属类的类名:aopdemo.TargetClass
目标方法声明类型:public
第1个参数为:newSpring
第2个参数为:newAop
被代理的对象:aopdemo.TargetClass@4efc180e
代理对象自己:aopdemo.TargetClass@4efc180e
目标方法返回结果后...
目标方法执行后...
result:newSpring+newAop

JoinPoint 的子类和关联类
MethodSignature:

MethodSignature 是 Signature 接口的子接口,专门用于方法调用。它提供了访问被拦截方法的详细信息,如方法名称、返回类型和参数类型。
在通知方法中,通常通过将 JoinPoint.getSignature() 的返回值强制转换为 MethodSignature 来获取更多关于方法的信息。
ProceedingJoinPoint:

ProceedingJoinPoint 是 JoinPoint 的子接口,专门用于环绕通知(@Around)。它添加了 proceed() 方法,允许控制何时继续执行拦截的方法。
proceed() 方法是环绕通知中的关键,它决定了是否继续执行原方法或者提前返回自定义结果。
————————————————

Spring Boot 条件注解:@ConditionalOnProperty 完全解析

在 Spring Boot 项目中,有时候我们希望根据配置文件中的某个属性值来决定是否启用某个功能或加载某个组件。此时,@ConditionalOnProperty 注解就可以发挥作用。它通过配置文件的属性值控制 Bean 或配置类的加载,使得我们的程序更具灵活性。

本文将详细介绍 @ConditionalOnProperty 的用法,并通过 功能开关 和 环境配置 两个实际场景来展示它的强大之处。

1. @ConditionalOnProperty 基本用法
语法
@ConditionalOnProperty(
prefix = "前缀",
name = "属性名",
havingValue = "指定值",
matchIfMissing = false
)
1
2
3
4
5
6
参数说明:

prefix:属性的前缀部分。
name:属性名称。
havingValue:属性的值与 havingValue 相等时条件成立(默认不指定)。
matchIfMissing:如果属性未定义,是否加载配置(默认 false,即未定义时不加载)。
2. 实战场景
场景一:功能开关
在实际项目中,我们可能需要通过配置文件中的某个属性来控制某个功能的启用或禁用。比如,是否开启定时任务、是否启用某个服务等。

示例:通过功能开关启用日志增强功能
Step 1:配置文件定义开关

在 application.properties 文件中添加一个开关属性:

feature.logging-enhancement.enabled=true
1
Step 2:实现日志增强功能

使用 @ConditionalOnProperty 来决定是否加载日志增强的 Bean:

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnProperty(prefix = "feature.logging-enhancement", name = "enabled", havingValue = "true", matchIfMissing = false)
public class LoggingEnhancementConfig {

@Bean
public String loggingEnhancement() {
System.out.println("日志增强功能已启用!");
return "Logging Enhancement Activated";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Step 3:测试

当 feature.logging-enhancement.enabled=true 时,LoggingEnhancementConfig 类会被加载,控制台会输出:

日志增强功能已启用!
1
当 feature.logging-enhancement.enabled=false 或未配置时,LoggingEnhancementConfig 类不会被加载。

场景二:环境配置
在不同的环境(开发、测试、生产)中,我们可能需要加载不同的配置。例如,开发环境下使用内存数据库,生产环境下使用 MySQL 数据库。

示例:不同环境下选择数据源
Step 1:配置文件

在 application.properties 中配置环境标识:

# 开发环境
spring.datasource.env=dev

# 生产环境
# spring.datasource.env=prod
1
2
3
4
5
Step 2:开发环境数据源配置

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

@Configuration
@ConditionalOnProperty(prefix = "spring.datasource", name = "env", havingValue = "dev")
public class DevDataSourceConfig {

@Bean
public DataSource devDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:testdb");
dataSource.setUsername("sa");
dataSource.setPassword("");
System.out.println("开发环境:加载内存数据库");
return dataSource;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Step 3:生产环境数据源配置

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

@Configuration
@ConditionalOnProperty(prefix = "spring.datasource", name = "env", havingValue = "prod")
public class ProdDataSourceConfig {

@Bean
public DataSource prodDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/proddb");
dataSource.setUsername("root");
dataSource.setPassword("password");
System.out.println("生产环境:加载 MySQL 数据库");
return dataSource;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Step 4:测试

开发环境:spring.datasource.env=dev
控制台输出:

开发环境:加载内存数据库
1
生产环境:spring.datasource.env=prod
控制台输出:

生产环境:加载 MySQL 数据库
1
3. 常见应用场景总结
功能开关:动态启用或禁用某个功能模块(如定时任务、监控服务等)。
环境配置:根据不同环境加载不同的配置(如数据源、日志级别等)。
组件选择:根据配置加载特定的第三方组件(如不同的缓存实现 Redis/ehcache)。
服务切换:实现备用服务或降级服务的自动切换。
4. 小结
@ConditionalOnProperty 是 Spring Boot 中非常实用的条件注解,可以通过配置文件灵活地控制 Bean 和配置类的加载,避免不必要的资源浪费,并提高系统的可维护性。

通过功能开关和环境配置的示例,我们可以看到 @ConditionalOnProperty 如何让代码更清晰、配置更灵活,极大地满足了开发者在不同场景下的需求。

最佳实践
配置文件中使用统一的前缀管理属性,避免冲突。
开关属性的命名要清晰直观,比如 feature.xxx.enabled。
对于重要的功能开关,可以结合文档明确其作用和默认值。

-------------------------------------------------------------

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/900944.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用gradio快速实现聊天机器人

我们可以使用gradio库通过低代码的方式快速实现聊天机器人界面及交互: import gradio as gr from ollama import chatdef predict(message,history):stream = chat(model="deepseek-r1:1.5b",messages=[{"role":"user","content":mess…

Edge浏览器登录微软账户报错0x80190001的解决办法

问题 0x80190001是什么错误?有用户在Edge浏览器上登录微软账户遇到了这个错误代码,这是什么意思?要如何解决呢?一个比较靠谱的解决办法解决方式1、下载一个finder(抓包软件)官网下载地址(最新版本):https://www.telerik.com/download/fiddler2、直接安装就好,选择左上…

2025年项目管理软件革命:7大工具重新定义团队协作

本文深度解析2025年项目管理工具的技术革新与市场格局,聚焦AI、量子计算、混合现实等技术对团队协作模式的颠覆性影响。一、2025年项目管理生态的三大颠覆性变化 在生成式AI、量子计算与混合现实技术推动下,项目管理工具正经历三大变革:决策权转移:AI从辅助工具升级为“虚拟…

【前瞻技术布局】京东零售广告创意:引入场域目标的创意图片生成

作者:京东零售 冯伟WWW2025: CTR-Driven Advertising Image Generation with Multimodal Large Language Models 论文链接:https://arxiv.org/pdf/2502.06823 代码链接:https://github.com/Chenguoz/CAIG 摘要:在电商平台中,广告图片对于吸引用户注意力和提高广告效果至关…

【达达秒送】AI运营视觉设计实战总结

作者:京东零售 刘潇蔓 全篇将从4个方面为大家系统介绍,我们如何使用AI,在运营活动中使用技法提质提效的… 1、AI在运营设计中的优势2、典型应用场景&案例3、AI工具核心关键词公式4、避坑指南:AI落地问题

vue3 slot 具名插槽

一、情景 一个组件用多个子组件,子组件样式相同 二、作用 父组件传递数据和格式给子组件 三、语法 1、子组件(挖坑)<slot name="title">默认标题</slot> <slot name="content">默认内容</slot>2、父组件(填坑) 用语法糖 #<Cat…

INFINI Labs 产品更新 - Coco AI – 增强 AI 搜索、API 管理与性能优化等

INFINI Labs 产品更新发布!此次更新涵盖 Coco AI 、Easysearch 等产品多项重要升级,重点提升 AI 搜索能力、易用性及企业级优化。Coco AI v0.2 作为 开源、跨平台的 AI 搜索工具,新增 APP 自动更新提示、API Token 管理、文档处理优化 等功能。 INFINI Easysearch v1.11.1 集…

无线数据网关 自动化测控的LoRa-4G混合网络 串口升级、信号扩展 高效物联传输网络

DLS11无线数据网关 自动化测控的LoRa-4G混合网络 串口升级、信号扩展 高效物联传输网络DLS11是一款专为VSxxx系列采发仪设计的内置电池低功耗数据转发器,支持LoRA和LTE(4G)无线通信。该设备通过“实时在线”的LoRA收发器,能够收集并存储来自其他LoRA设备的数据。随后,DLS1…

魔搭通义灵码:0代码基础、0门槛在线编程做应用

在具体开发过程中,好的开发环境和开发工具十分重要,为了更好的体验,更好地利用AI进行代码生成,我们推荐使用魔搭社区 Notebook云端开发 IDE 工具,搭配内置的 Vscode 编程环境以及通义灵码工具,一站式进行AI应用搭建。不需要复杂的环境配置,也不需要代码基础,只要熟练运…

ASE69N30-ASEMI智能工业专用ASE69N30

ASE69N30-ASEMI智能工业专用ASE69N30编辑:ll ASE69N30-ASEMI智能工业专用ASE69N30 型号:ASE69N30 品牌:ASEMI 封装:TO-247 批号:最新 最大漏源电流:69A 漏源击穿电压:300V RDS(ON)Max:49mΩ 引脚数量:3 沟道类型:N沟道MOS管、中低压MOS管 漏电流:ua 特性:N沟道MO…

PLM项目管理软件如何改善设计变更管理?

设计变更在产品的生命周期中是不可避免的,它可能源于客户需求的变化、技术的更新、原材料的供应问题等多种因素。有效的设计变更管理对于确保产品按时交付、控制成本以及维持产品质量至关重要。PLM(产品生命周期管理)项目管理软件作为一种强大的工具,能够显著改善设计变更管…

4.3D曲线

1.多项式参数曲线 1.1参数曲线 曲线可以用一个带有参数$t$的函数来描述,函数形式为$p(t)$,输入的参数为$t$。 例子1:单位圆的经典参数描述 其中$0\leq t\leq1$ $x(t) = \cos(2\pi t)$ $y(t) = \sin(2\pi t)$ 例子2:中心点在原点的椭圆参数方程 其中$0\leq t \leq 1$,a是椭圆…