
在spring boot开发中,我们经常遇到需要在多个方法或类中重复执行某段横切逻辑(如日志记录、权限校验、事务管理或数据预处理)。如果直接将这些逻辑硬编码到每个方法中,会导致代码冗余、可维护性差。理想情况下,我们希望能够通过一个简单的标记(自定义注解)来声明性地应用这些逻辑,而无需修改原始方法体。例如,当一个controller类被特定注解标记时,其内部的某些处理方法能够自动地向model中添加预设数据。这正是spring aop与自定义注解的完美结合点。
Spring AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在将横切关注点(如日志、事务、安全等)从核心业务逻辑中分离出来。它允许我们在不修改核心业务代码的情况下,通过“切面”来增强或修改现有代码的行为。
Spring AOP的关键概念包括:
首先,我们需要创建一个自定义注解,用作我们逻辑增强的标记。
package com.example.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,用于标记需要额外逻辑增强的Controller或方法
*/
@Target(ElementType.TYPE) // 目标是类、接口(包括注解类型)或枚举声明
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时可用,可以通过反射读取
public @interface MyCustomAnnotation {
// 可以在这里定义注解的属性,例如一个描述信息
String value() default "Default custom logic";
}注解元数据解释:
接下来,我们将创建一个Spring AOP切面,它将定义切点(哪些方法需要被拦截)和通知(拦截后执行什么逻辑)。
package com.example.aspect;
import com.example.annotation.MyCustomAnnotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
/**
* 自定义注解的切面,用于实现逻辑增强
*/
@Aspect // 声明这是一个切面
@Component // 将切面作为Spring组件管理
public class CustomAnnotationAspect {
/**
* 定义一个环绕通知(@Around),拦截被MyCustomAnnotation注解的类中所有公共方法。
*
* 切点表达式解释:
* - @within(com.example.annotation.MyCustomAnnotation):匹配所有被MyCustomAnnotation注解的类。
* - execution(public * *(..)):匹配该类中所有的公共方法。
*
* @param joinPoint 连接点,提供对被拦截方法的信息和控制
* @return 目标方法的返回值
* @throws Throwable 目标方法或切面逻辑可能抛出的异常
*/
@Around("@within(com.example.annotation.MyCustomAnnotation) && execution(public * *(..))")
public Object applyCustomLogic(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("--- 进入自定义逻辑增强切面 ---");
// 1. 在方法执行前添加逻辑
System.out.println("切面:准备为方法 " + joinPoint.getSignature().getName() + " 添加Model属性...");
// 获取方法参数,查找Model对象
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof Model) {
Model model = (Model) arg;
// 根据需求添加Model属性
model.addAttribute("customKey", "customValueFromAspect");
System.out.println("切面:成功向Model中添加属性 'customKey:customValueFromAspect'");
break; // 假设每个方法最多只有一个Model参数
}
}
// 2. 执行原始目标方法
Object result = joinPoint.proceed(); // 调用目标方法
// 3. 在方法执行后添加逻辑(可选)
System.out.println("切面:方法 " + joinPoint.getSignature().getName() + " 执行完毕。");
System.out.println("--- 退出自定义逻辑增强切面 ---");
return result; // 返回目标方法的执行结果
}
}切点(Pointcut)定义与通知(Advice)类型:
实现逻辑注入:获取并操作Model对象: 在applyCustomLogic方法内部,我们通过joinPoint.getArgs()获取到目标方法的所有参数。然后遍历这些参数,判断是否存在org.springframework.ui.Model类型的实例。如果找到,就将其强制转换为Model类型,并调用addAttribute()方法注入我们需要的键值对。
为了让Spring Boot应用能够识别并应用我们定义的切面,我们需要在主应用类或配置类上添加@EnableAspectJAutoProxy注解。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy // 启用Spring AOP的自动代理功能
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}现在,我们创建一个Controller来演示如何使用自定义注解。
package com.example.controller;
import com.example.annotation.MyCustomAnnotation;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 示例Controller,被MyCustomAnnotation标记
*/
@MyCustomAnnotation // 应用自定义注解
@Controller
public class ExController {
@RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model){
System.out.println("Controller:进入index方法。");
// 这里不需要手动添加model.addAttribute("customKey","customValueFromAspect");
// 它会由切面自动添加
// 验证切面是否已添加属性
if (model.containsAttribute("customKey")) {
System.out.println("Controller:Model中已包含 'customKey',值为:" + model.getAttribute("customKey"));
} else {
System.out.println("Controller:Model中未包含 'customKey'。");
}
model.addAttribute("message", "Hello from ExController!");
return "index"; // 返回视图名称
}
@GetMapping("/hello")
public String hello(Model model) {
System.out.println("Controller:进入hello方法。");
// 这个方法也会被切面拦截,因为MyCustomAnnotation在类级别
if (model.containsAttribute("customKey")) {
System.out.println("Controller:Model中已包含 'customKey',值为:" + model.getAttribute("customKey"));
}
model.addAttribute("greeting", "Greetings from another method!");
return "hello";
}
@GetMapping("/no-model")
public String noModel() {
System.out.println("Controller:进入noModel方法,无Model参数。");
// 这个方法也会被切面拦截,但切面不会找到Model参数,所以不会添加属性
return "no-model";
}
}当请求/index或/hello时,CustomAnnotationAspect会拦截这些方法的执行,并在它们内部逻辑执行前,自动向Model对象中添加"customKey":"customValueFromAspect"属性。而/no-model方法虽然也被拦截,但由于其参数中不包含Model对象,切面不会执行model.addAttribute()操作。
通过自定义注解结合Spring AOP,我们成功地实现了一种声明式的方式来增强Spring Boot应用中的方法逻辑。这种模式极大地提高了代码的模块化、可维护性和复用性,使得横切关注点与核心业务逻辑清晰分离。无论是进行权限管理、日志记录、性能监控,还是像本例中动态添加Model属性,Spring AOP都提供了一个优雅且强大的解决方案。掌握这一技术,将使你的Spring Boot应用开发更加高效和专业。
以上就是利用Spring AOP与自定义注解实现方法逻辑扩展的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号