0

0

通过自定义注解在 Spring Boot 方法中注入特定逻辑

DDD

DDD

发布时间:2025-09-22 11:25:00

|

451人浏览过

|

来源于php中文网

原创

通过自定义注解在 spring boot 方法中注入特定逻辑

本文深入探讨了如何在 Spring Boot 应用中,利用自定义注解结合 Spring AOP(面向切面编程)动态地为方法注入特定业务逻辑。通过定义一个标记注解、创建切面并配置相应的通知,开发者可以实现代码的解耦和复用,避免在每个方法内部手动添加重复代码,从而提升代码的可维护性和扩展性。教程将详细介绍注解的创建、切面的实现以及 AOP 的启用,并提供完整的代码示例。

1. 理解需求:通过标记注解注入逻辑

在 Spring Boot 开发中,有时我们希望在某些方法执行前后自动添加一段通用逻辑,例如日志记录、权限校验、缓存处理或像用户示例中那样,向 Model 对象添加特定属性。如果每次都在方法内部手动添加这些逻辑,不仅代码会变得冗余,而且难以维护。理想的解决方案是使用一个自定义注解作为标记,然后通过某种机制(如 AOP)在运行时根据这个标记自动注入所需的逻辑。

用户示例中展示的需求是,当一个类或方法被 @MyCustomAnnotation 标记时,其内部的 Model 对象应自动添加 model.addAttribute("key","value"); 这段逻辑。

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

Spring AOP(Aspect-Oriented Programming)是解决这类问题的核心机制。它允许我们定义“切面”(Aspect),在切面中定义“切点”(Pointcut)来匹配特定的执行点(如方法执行),并定义“通知”(Advice)来在这些切点上执行额外的逻辑。

核心步骤:

  1. 定义自定义注解: 作为逻辑注入的触发器。
  2. 创建切面: 包含切点和通知,实现具体的逻辑注入。
  3. 启用 AOP: 确保 Spring 容器能够识别并应用切面。

3. 实现步骤详解

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

首先,我们需要定义一个简单的自定义注解,它将作为我们逻辑注入的标记。这个注解不需要包含任何业务逻辑,它只是一个元数据标记。

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) // 运行时保留,以便通过反射读取
public @interface MyCustomAnnotation {
    // 可以在此添加属性,如果逻辑需要基于注解的参数
    // String value() default "";
}
  • @Target({ElementType.TYPE, ElementType.METHOD}):指定该注解可以应用于类(TYPE)和方法(METHOD)。用户示例中注解应用于类,但为灵活性考虑,通常也允许方法级别。
  • @Retention(RetentionPolicy.RUNTIME):指定注解在运行时可用,这是 AOP 能够通过反射读取注解信息并应用逻辑的前提。

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

切面是实现核心逻辑的地方。它包含一个切点定义,用于匹配被 @MyCustomAnnotation 标记的类或方法,以及一个通知,用于在匹配到时执行具体的业务逻辑。

讯飞智文
讯飞智文

一键生成PPT和Word,让学习生活更轻松。

下载
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.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;

import java.util.Arrays;

/**
 * 自定义注解逻辑切面,用于在被@MyCustomAnnotation标记的方法执行前注入逻辑。
 */
@Aspect // 声明这是一个切面
@Component // 将切面作为Spring组件管理
public class CustomAnnotationLogicAspect {

    /**
     * 定义切点:匹配被 @MyCustomAnnotation 标记的类中的所有方法,
     * 或者直接被 @MyCustomAnnotation 标记的方法。
     */
    @Pointcut("@within(com.example.demo.annotation.MyCustomAnnotation) || @annotation(com.example.demo.annotation.MyCustomAnnotation)")
    public void myCustomAnnotationPointcut() {}

    /**
     * 定义环绕通知:在目标方法执行前后执行自定义逻辑。
     *
     * @param joinPoint 连接点,提供对目标方法的信息和控制
     * @return 目标方法的返回值
     * @throws Throwable 目标方法或通知中抛出的异常
     */
    @Around("myCustomAnnotationPointcut()")
    public Object applyCustomLogic(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("--- 进入 CustomAnnotationLogicAspect: 准备注入逻辑 ---");

        // 尝试从方法参数中查找 Model 对象,并添加属性
        boolean modelModified = false;
        Object[] args = joinPoint.getArgs(); // 获取目标方法的参数
        if (args != null && args.length > 0) {
            for (Object arg : args) {
                if (arg instanceof Model) { // 如果参数是 Model 类型
                    Model model = (Model) arg;
                    model.addAttribute("keyFromAspect", "valueFromCustomAnnotation");
                    System.out.println("    >>> 成功向 Model 添加属性: keyFromAspect='valueFromCustomAnnotation'");
                    modelModified = true;
                    break;
                }
            }
        }

        if (!modelModified) {
            System.out.println("    >>> 警告: 未在方法参数中找到 Model 对象,无法注入 Model 属性。");
        }

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

        System.out.println("--- 退出 CustomAnnotationLogicAspect: 逻辑注入完成 ---");
        return result; // 返回目标方法的执行结果
    }
}
  • @Aspect:将一个类声明为切面。
  • @Component:将切面类注册为 Spring Bean,以便 Spring 能够发现并管理它。
  • @Pointcut:定义切点表达式。
    • @within(com.example.demo.annotation.MyCustomAnnotation):匹配所有被 MyCustomAnnotation 注解的类中的所有方法。这符合用户示例中注解在类上的情况。
    • @annotation(com.example.demo.annotation.MyCustomAnnotation):匹配所有直接被 MyCustomAnnotation 注解的方法。
    • ||:表示逻辑或,即满足任一条件即可。
  • @Around("myCustomAnnotationPointcut()"):定义一个环绕通知。环绕通知是最强大的通知类型,它可以在目标方法执行之前、之后以及出现异常时执行逻辑,并且可以控制目标方法是否执行。
  • ProceedingJoinPoint:环绕通知的参数,提供了访问目标方法信息(如参数、方法名)和控制目标方法执行(joinPoint.proceed())的能力。
  • 注入逻辑:applyCustomLogic 方法中,我们遍历目标方法的参数,查找 Model 类型的参数。一旦找到,就调用 model.addAttribute() 来注入所需的属性。

3.3 步骤三:启用 Spring AOP

为了让 Spring 容器能够识别并应用 @Aspect 注解定义的切面,我们需要在 Spring Boot 应用的主类或配置类上添加 @EnableAspectJAutoProxy 注解。

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy; // 导入 AOP 启用注解

@SpringBootApplication
@EnableAspectJAutoProxy // 启用 Spring AOP 代理
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

3.4 步骤四:应用自定义注解

现在,你可以在任何 Spring 管理的组件(如 Controller、Service、Repository)的类或方法上使用 @MyCustomAnnotation,相关的逻辑就会被自动注入。

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.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@MyCustomAnnotation // 将自定义注解应用于 Controller 类
@Controller
public class ExController {

    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public String index(Model model){
        // 注意:这里不需要手动添加 model.addAttribute("key","value");
        // 这段逻辑将由 CustomAnnotationLogicAspect 自动注入。
        System.out.println("    >>> 正在执行 ExController.index() 方法内部逻辑。");
        System.out.println("    >>> Model 是否包含 'keyFromAspect' 属性? " + model.containsAttribute("keyFromAspect"));
        return "index"; // 假设存在一个名为 'index.html' 或 'index.jsp' 的视图
    }

    @GetMapping("/another")
    public String anotherMethod(Model model) {
        // 由于类被 @MyCustomAnnotation 标记,此方法也会被切面处理
        System.out.println("    >>> 正在执行 ExController.anotherMethod() 方法内部逻辑。");
        System.out.println("    >>> Model 是否包含 'keyFromAspect' 属性? " + model.containsAttribute("keyFromAspect"));
        return "another"; // 假设存在一个名为 'another.html' 的视图
    }
}

当访问 /index 或 /another 路径时,控制台输出将显示切面逻辑被执行,并且 Model 对象中会包含由切面注入的 keyFromAspect 属性。

4. 注意事项与最佳实践

  • 注解粒度: 考虑你的逻辑应该作用于整个类下的所有方法,还是仅仅作用于特定的方法。如果只作用于特定方法,将 @MyCustomAnnotation 放在方法上更合适,并调整切点表达式。
  • 通知类型选择:
    • @Before:在目标方法执行前执行。
    • @AfterReturning:在目标方法成功返回后执行。
    • @AfterThrowing:在目标方法抛出异常后执行。
    • @After:无论目标方法成功或失败,都在其执行后执行。
    • @Around:最灵活,可以完全控制目标方法的执行,包括是否执行、何时执行以及修改其参数和返回值。本例中使用 @Around 是因为需要修改方法参数(Model)。
  • 参数访问: 在 @Around 通知中,通过 ProceedingJoinPoint 可以访问目标方法的参数 (joinPoint.getArgs()) 和修改返回值 (return joinPoint.proceed())。
  • 性能考量: AOP 通过代理实现,会增加一定的运行时开销。对于性能敏感的场景,应谨慎使用,并确保切点表达式足够精确,避免不必要的代理。
  • 异常处理: 在切面中处理逻辑时,要考虑可能发生的异常,并决定是继续抛出还是捕获处理。
  • 与 Spring MVC 拦截器对比: 对于 Web 请求的通用逻辑(如权限、日志),Spring MVC 的 HandlerInterceptor 也是一个选择。但 AOP 更加通用,可以应用于任何 Spring Bean 的方法,而不仅仅是 Controller 方法。当逻辑需要深入到业务层或数据访问层时,AOP 更具优势。

5. 总结

通过自定义注解结合 Spring AOP,我们能够以非侵入式的方式为 Spring Boot 应用中的方法注入通用逻辑。这种模式极大地提高了代码的模块化、可维护性和复用性。通过清晰地定义注解作为标记,并利用切面实现具体的逻辑,我们可以构建出更加优雅和健壮的应用程序。理解 AOP 的核心概念(切面、切点、通知)是掌握这一强大功能的关键。

相关专题

更多
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

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

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

2

2026.01.19

微信聊天记录删除恢复导出教程汇总
微信聊天记录删除恢复导出教程汇总

本专题整合了微信聊天记录相关教程大全,阅读专题下面的文章了解更多详细内容。

39

2026.01.18

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

101

2026.01.16

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
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号