0

0

Spring Boot中通过自定义注解注入方法逻辑的教程

碧海醫心

碧海醫心

发布时间:2025-09-22 11:20:42

|

457人浏览过

|

来源于php中文网

原创

spring boot中通过自定义注解注入方法逻辑的教程

本教程旨在详细阐述如何在Spring Boot应用中,利用自定义注解结合Spring AOP(面向切面编程)的机制,实现在不修改原有方法体的前提下,动态地向指定方法注入额外逻辑。文章将通过定义自定义注解、创建切面并编写通知(Advice)来匹配注解并执行增强操作,从而实现优雅且可维护的代码扩展。

1. 理解需求:基于自定义注解的逻辑注入

在Spring Boot开发中,我们常常遇到这样的场景:希望在某些特定方法执行前后或执行过程中,统一添加一些非核心业务逻辑,例如日志记录、权限校验、缓存处理,或者像本例中提出的,根据一个自定义注解来修改方法的参数(如Model对象)。直接修改每个目标方法无疑会造成代码冗余且难以维护。此时,Spring AOP与自定义注解的结合,提供了一种优雅的解决方案。

用户提出的问题是,如何在不直接修改index方法体的情况下,通过在ExController类或index方法上添加@MyCustomAnnotation注解,就能自动为Model添加一个属性,例如model.addAttribute("key","value")。这并非简单地在方法内部添加逻辑,也不是通过@RequestParam等参数绑定注解能解决的问题,而是需要一种“拦截”并“增强”目标方法执行的能力。

2. 核心解决方案:Spring AOP与自定义注解

Spring AOP(Aspect-Oriented Programming,面向切面编程)允许我们定义横切关注点(cross-cutting concerns),并将其从核心业务逻辑中分离出来。通过AOP,我们可以定义一个“切面”(Aspect),其中包含“通知”(Advice)和“切入点”(Pointcut)。自定义注解则可以作为我们定义切入点的强大工具,指示AOP框架在何处应用我们的增强逻辑。

其基本原理是:

  1. 定义自定义注解:用于标记需要被增强的类或方法。
  2. 创建切面:一个普通的Spring组件,用@Aspect注解标记。
  3. 定义切入点:使用@Pointcut注解结合自定义注解,指定哪些方法或类是我们的目标。
  4. 编写通知:使用@Before、@AfterReturning、@Around等注解,定义在切入点匹配的方法执行时,需要执行的额外逻辑。

3. 实现步骤

3.1 步骤一:定义自定义注解

首先,我们需要定义一个自定义注解@MyCustomAnnotation。这个注解将用于标记我们希望进行逻辑注入的类或方法。

package com.example.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解,用于标记需要额外逻辑处理的类或方法
 */
@Target({ElementType.TYPE, ElementType.METHOD}) // 作用于类和方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,AOP可以读取
public @interface MyCustomAnnotation {
    String key() default "defaultKey"; // 可选属性,用于在切面中获取值
    String value() default "defaultValue"; // 可选属性
}
  • @Target({ElementType.TYPE, ElementType.METHOD}):指定注解可以应用于类(TYPE)和方法(METHOD)。
  • @Retention(RetentionPolicy.RUNTIME):表示注解在运行时可用,这是AOP能够通过反射读取注解信息的前提。

3.2 步骤二:创建切面(Aspect)

接下来,创建一个切面类,它将包含我们的切入点和通知逻辑。

package com.example.demo.aspect;

import com.example.demo.annotation.MyCustomAnnotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;

import java.lang.reflect.Method;

/**
 * 自定义注解的切面,用于注入额外逻辑
 */
@Aspect // 标记这是一个切面
@Component // 注册为Spring组件
public class MyCustomAnnotationAspect {

    /**
     * 定义一个环绕通知,匹配所有带有 @MyCustomAnnotation 注解的方法
     * 或者其所属类带有 @MyCustomAnnotation 注解的方法
     *
     * @param joinPoint 提供了访问目标方法信息的接口
     * @param myCustomAnnotation 匹配到的自定义注解实例
     * @return 目标方法的返回值
     * @throws Throwable 目标方法抛出的异常
     */
    @Around("(@annotation(myCustomAnnotation) || @within(myCustomAnnotation)) && execution(* *(..))")
    public Object applyCustomLogic(ProceedingJoinPoint joinPoint, MyCustomAnnotation myCustomAnnotation) throws Throwable {
        System.out.println("--- 进入自定义注解切面 ---");

        // 获取目标方法的参数
        Object[] args = joinPoint.getArgs();
        // 获取方法签名,以便获取方法上的注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 尝试从方法或类上获取注解实例,以获取其属性值
        MyCustomAnnotation annotation = method.getAnnotation(MyCustomAnnotation.class);
        if (annotation == null) {
            // 如果方法上没有,则尝试从类上获取
            annotation = joinPoint.getTarget().getClass().getAnnotation(MyCustomAnnotation.class);
        }

        String key = (annotation != null) ? annotation.key() : "defaultKeyFromAspect";
        String value = (annotation != null) ? annotation.value() : "defaultValueFromAspect";

        // 遍历方法参数,查找 Model 类型的参数并进行修改
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Model) {
                Model model = (Model) args[i];
                model.addAttribute(key, value);
                System.out.println("切面已向 Model 添加属性: " + key + " = " + value);
                // 可以根据需求修改其他参数
                // args[i] = newModelInstance;
            }
        }

        // 执行目标方法
        Object result = joinPoint.proceed(args);

        System.out.println("--- 退出自定义注解切面 ---");
        return result;
    }
}
  • @Aspect:将该类声明为一个切面。
  • @Component:将其注册为Spring容器管理的Bean。
  • @Around("(@annotation(myCustomAnnotation) || @within(myCustomAnnotation)) && execution(* *(..))"):这是切入点表达式和通知类型。
    • @annotation(myCustomAnnotation):匹配直接在方法上带有@MyCustomAnnotation注解的方法。myCustomAnnotation会作为参数传递给通知方法。
    • @within(myCustomAnnotation):匹配其所属类带有@MyCustomAnnotation注解的所有方法。
    • ||:表示“或”关系,即方法或其类带有注解即可。
    • execution(* *(..)):匹配所有方法执行。
    • ProceedingJoinPoint joinPoint:环绕通知特有的参数,用于控制目标方法的执行。
    • MyCustomAnnotation myCustomAnnotation:AOP框架会自动将匹配到的注解实例注入到通知方法中,方便我们获取注解的属性值。
  • 在通知内部,我们:
    1. 获取目标方法的参数数组。
    2. 遍历参数,查找Model类型的参数。
    3. 如果找到Model,则使用从MyCustomAnnotation中获取的key和value属性,向Model添加数据。
    4. joinPoint.proceed(args):这是关键!它会执行目标方法。你可以选择在执行前、执行后、甚至不执行目标方法。

3.3 步骤三:应用自定义注解到控制器

现在,我们可以在Spring Boot的控制器中使用这个自定义注解了。

package com.example.demo.controller;

import com.example.demo.annotation.MyCustomAnnotation;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@MyCustomAnnotation(key = "controllerKey", value = "controllerValue") // 注解在类上
@Controller
public class ExController {

    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index(Model model){
        // 这里的 model.addAttribute("controllerKey", "controllerValue") 将由切面自动添加
        System.out.println("--- 进入 ExController.index 方法 ---");
        System.out.println("Model 中是否包含 'controllerKey': " + model.containsAttribute("controllerKey"));
        return "index"; // 假设有一个名为 index.html 的 Thymeleaf/JSP 模板
    }

    @MyCustomAnnotation(key = "methodKey", value = "methodValue") // 注解在方法上
    @RequestMapping(value = "/data", method = RequestMethod.GET)
    @ResponseBody
    public String getData(Model model) {
        // 这里的 model.addAttribute("methodKey", "methodValue") 将由切面自动添加
        System.out.println("--- 进入 ExController.getData 方法 ---");
        System.out.println("Model 中是否包含 'methodKey': " + model.containsAttribute("methodKey"));
        return "Data fetched successfully!";
    }

    @RequestMapping(value = "/no-annotation", method = RequestMethod.GET)
    public String noAnnotationMethod(Model model) {
        // 此方法没有直接或间接的自定义注解,因此不会被切面增强
        System.out.println("--- 进入 ExController.noAnnotationMethod 方法 ---");
        System.out.println("Model 中是否包含 'controllerKey': " + model.containsAttribute("controllerKey")); // 应该为 false
        return "no-annotation";
    }
}

在这个例子中:

  • ExController类上标记了@MyCustomAnnotation(key = "controllerKey", value = "controllerValue"),因此index方法会受到切面的影响。
  • getData方法上标记了@MyCustomAnnotation(key = "methodKey", value = "methodValue"),它将覆盖类上的注解,使用自己的key和value。
  • noAnnotationMethod方法和其所属类都没有@MyCustomAnnotation,因此不会被切面拦截。

3.4 步骤四:确保AOP依赖和配置

在Spring Boot项目中,通常只需要添加spring-boot-starter-aop依赖即可自动启用AOP支持。

千问APP
千问APP

阿里最强大模型官方AI助手

下载

在pom.xml中添加:


    org.springframework.boot
    spring-boot-starter-aop

Spring Boot会自动配置AspectJ的自动代理。无需手动添加@EnableAspectJAutoProxy注解。

4. 运行与验证

当您启动Spring Boot应用并访问/index或/data路径时,您会观察到控制台输出:

访问 /index 路径的输出示例:

--- 进入自定义注解切面 ---
切面已向 Model 添加属性: controllerKey = controllerValue
--- 进入 ExController.index 方法 ---
Model 中是否包含 'controllerKey': true
--- 退出自定义注解切面 ---

访问 /data 路径的输出示例:

--- 进入自定义注解切面 ---
切面已向 Model 添加属性: methodKey = methodValue
--- 进入 ExController.getData 方法 ---
Model 中是否包含 'methodKey': true
--- 退出自定义注解切面 ---

访问 /no-annotation 路径的输出示例:

--- 进入 ExController.noAnnotationMethod 方法 ---
Model 中是否包含 'controllerKey': false

这证明了切面成功地根据自定义注解,在不修改控制器方法的情况下,向Model对象注入了数据。

5. 注意事项与最佳实践

  • AOP的适用场景:AOP非常适合处理横切关注点,如日志、事务管理、权限校验、缓存、性能监控等。避免将核心业务逻辑放入切面中,以保持业务代码的清晰。
  • 注解粒度:根据需求选择将注解应用于类、方法还是两者。类级别注解可以为整个类的方法提供统一的增强,方法级别注解则提供更细粒度的控制。
  • 通知类型选择
    • @Before:在目标方法执行前执行,不影响方法执行。
    • @AfterReturning:在目标方法成功返回后执行,可以访问返回值。
    • @AfterThrowing:在目标方法抛出异常后执行。
    • @After:在目标方法执行(无论成功或异常)后执行。
    • @Around:最强大的通知,可以完全控制目标方法的执行,包括是否执行、何时执行、如何修改参数和返回值。本例中修改Model就需要@Around。
  • 代理机制:Spring AOP默认使用JDK动态代理(针对接口)或CGLIB代理(针对类)。这意味着切面只能作用于通过Spring容器管理的Bean。
  • 避免过度使用:虽然AOP功能强大,但过度使用可能导致代码难以理解和调试,因为逻辑不再是线性执行。确保切面逻辑是真正的“横切”关注点。
  • 注解属性的利用:自定义注解可以包含属性,这些属性可以在切面中被读取,从而实现更灵活的逻辑注入。例如,@MyCustomAnnotation(cacheName="userCache", ttl=300)。

6. 总结

通过本教程,我们学习了如何利用Spring Boot的AOP机制,结合自定义注解,优雅地向现有方法注入额外逻辑。这种方式避免了对业务代码的侵入性修改,提高了代码的模块化和可维护性。掌握AOP是Spring高级开发中的一项重要技能,它能帮助我们构建更加健壮和可扩展的企业级应用。

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

103

2025.08.06

spring boot框架优点
spring boot框架优点

spring boot框架的优点有简化配置、快速开发、内嵌服务器、微服务支持、自动化测试和生态系统支持。本专题为大家提供spring boot相关的文章、下载、课程内容,供大家免费下载体验。

135

2023.09.05

spring框架有哪些
spring框架有哪些

spring框架有Spring Core、Spring MVC、Spring Data、Spring Security、Spring AOP和Spring Boot。详细介绍:1、Spring Core,通过将对象的创建和依赖关系的管理交给容器来实现,从而降低了组件之间的耦合度;2、Spring MVC,提供基于模型-视图-控制器的架构,用于开发灵活和可扩展的Web应用程序等。

389

2023.10.12

Java Spring Boot开发
Java Spring Boot开发

本专题围绕 Java 主流开发框架 Spring Boot 展开,系统讲解依赖注入、配置管理、数据访问、RESTful API、微服务架构与安全认证等核心知识,并通过电商平台、博客系统与企业管理系统等项目实战,帮助学员掌握使用 Spring Boot 快速开发高效、稳定的企业级应用。

68

2025.08.19

Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性
Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性

Spring Boot 是一个基于 Spring 框架的 Java 开发框架,它通过 约定优于配置的原则,大幅简化了 Spring 应用的初始搭建、配置和开发过程,让开发者可以快速构建独立的、生产级别的 Spring 应用,无需繁琐的样板配置,通常集成嵌入式服务器(如 Tomcat),提供“开箱即用”的体验,是构建微服务和 Web 应用的流行工具。

33

2025.12.22

Java Spring Boot 微服务实战
Java Spring Boot 微服务实战

本专题深入讲解 Java Spring Boot 在微服务架构中的应用,内容涵盖服务注册与发现、REST API开发、配置中心、负载均衡、熔断与限流、日志与监控。通过实际项目案例(如电商订单系统),帮助开发者掌握 从单体应用迁移到高可用微服务系统的完整流程与实战能力。

114

2025.12.24

pdf怎么转换成xml格式
pdf怎么转换成xml格式

将 pdf 转换为 xml 的方法:1. 使用在线转换器;2. 使用桌面软件(如 adobe acrobat、itext);3. 使用命令行工具(如 pdftoxml)。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1883

2024.04.01

xml怎么变成word
xml怎么变成word

步骤:1. 导入 xml 文件;2. 选择 xml 结构;3. 映射 xml 元素到 word 元素;4. 生成 word 文档。提示:确保 xml 文件结构良好,并预览 word 文档以验证转换是否成功。想了解更多xml的相关内容,可以阅读本专题下面的文章。

2087

2024.08.01

PHP WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

3

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7万人学习

Java 教程
Java 教程

共578课时 | 47.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号