0

0

利用自定义注解在Spring Boot中实现方法逻辑的动态增强

聖光之護

聖光之護

发布时间:2025-09-22 13:31:20

|

796人浏览过

|

来源于php中文网

原创

利用自定义注解在spring boot中实现方法逻辑的动态增强

本文旨在探讨如何在Spring Boot应用中,通过自定义注解结合Spring AOP(面向切面编程)机制,实现对特定方法的行为动态增强,而无需在方法内部显式编写额外逻辑。我们将详细介绍如何创建自定义注解、定义切面以及编写相应的通知,以在方法执行前后或执行过程中插入预设的业务逻辑,从而提升代码的模块化和可维护性。

1. 引言与背景

在Spring Boot开发中,我们经常会遇到需要在多个方法中重复执行某些横切关注点(如日志记录、权限校验、事务管理或数据预处理等)的需求。如果将这些逻辑直接嵌入到每个业务方法中,会导致代码冗余、可读性下降,并且难以维护。用户提出的需求是希望通过一个自定义注解来“标记”一个方法或类,然后系统能够自动为这些被标记的方法添加特定的逻辑,例如在Controller方法执行前向Model中添加数据。这种需求非常适合使用Spring AOP来解决。

Spring AOP允许开发者定义横切关注点,并将其从核心业务逻辑中分离出来,通过“切面”的形式在程序运行时动态地织入到目标对象中。结合自定义注解,我们可以精确地指定哪些方法或类应该被这些横切逻辑所增强。

2. 定义自定义注解

首先,我们需要创建一个自定义注解,用作标记点。这个注解可以没有任何属性,或者包含一些配置属性,以便切面能够根据这些属性进行不同的处理。

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;

/**
 * 自定义注解:用于标记需要额外添加Model属性的Controller方法或类
 */
@Target({ElementType.METHOD, ElementType.TYPE}) // 作用于方法和类
@Retention(RetentionPolicy.RUNTIME) // 运行时可见
public @interface AddCustomModelAttr {
    /**
     * 要添加的Model属性的键
     */
    String key() default "defaultKey";

    /**
     * 要添加的Model属性的值
     */
    String value() default "defaultValue";
}

注解说明:

  • @Target({ElementType.METHOD, ElementType.TYPE}):表示这个注解可以应用于方法和类上。如果只希望作用于方法,可以移除ElementType.TYPE。
  • @Retention(RetentionPolicy.RUNTIME):表示这个注解在运行时是可用的,这样AOP切面才能通过反射机制读取到它。
  • key() 和 value():我们为注解添加了两个属性,用于指定要添加到Model中的键值对。它们都有默认值,方便使用。

3. 创建切面(Aspect)

接下来,我们将创建一个切面类,其中包含切点(Pointcut)和通知(Advice)。切点定义了哪些方法会被拦截,而通知则包含了要执行的额外逻辑。

package com.example.demo.aspect;

import com.example.demo.annotation.AddCustomModelAttr;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 自定义Model属性添加切面
 */
@Aspect
@Component
public class CustomModelAttrAspect {

    /**
     * 定义切点:匹配所有被 @AddCustomModelAttr 注解标记的方法或类
     * 注意:这里为了简化示例,我们假设Controller方法会接收Model参数。
     * 实际应用中可能需要更复杂的逻辑来获取Model对象。
     */
    @Before("@annotation(addCustomModelAttr) || @within(addCustomModelAttr)")
    public void addModelAttributes(JoinPoint joinPoint, AddCustomModelAttr addCustomModelAttr) {
        // 尝试从方法参数中获取Model对象
        Object[] args = joinPoint.getArgs();
        Model model = null;

        for (Object arg : args) {
            if (arg instanceof Model) {
                model = (Model) arg;
                break;
            }
        }

        // 如果方法参数中没有Model对象,则尝试从ModelAndView中获取(如果返回类型是ModelAndView)
        // 或者通过其他方式(如RequestAttributes)来传递数据。
        // 这里为了示例的简洁性,我们假设Model总是作为参数传入。
        if (model == null) {
            // 尝试从请求属性中获取Model,这通常不是直接获取Model的最佳方式,
            // 但可以作为一种备选方案或用于调试。
            // 实际场景中,如果Model不是参数,可能需要改变切点或通知类型。
            System.out.println("警告:目标方法没有Model参数,无法直接添加属性。");
            return;
        }

        // 获取注解的属性值
        String key = addCustomModelAttr.key();
        String value = addCustomModelAttr.value();

        // 向Model中添加属性
        model.addAttribute(key, value);
        System.out.println("AOP: 成功向Model中添加属性 - key: " + key + ", value: " + value);
    }
}

切面说明:

  • @Aspect 和 @Component:将这个类声明为一个Spring管理的切面。
  • @Before("@annotation(addCustomModelAttr) || @within(addCustomModelAttr)"):这是切点表达式和通知类型。
    • @Before:表示在目标方法执行 之前 运行通知逻辑。
    • @annotation(addCustomModelAttr):匹配所有被 @AddCustomModelAttr 注解直接标记的方法。
    • @within(addCustomModelAttr):匹配所有被 @AddCustomModelAttr 注解标记的类中的所有方法。
    • addCustomModelAttr:将匹配到的注解实例作为参数传递给通知方法,以便我们可以访问注解的属性。
  • addModelAttributes(JoinPoint joinPoint, AddCustomModelAttr addCustomModelAttr):通知方法。
    • JoinPoint:提供了关于被拦截方法的信息,如方法参数 (getArgs())。
    • 通过遍历方法参数,我们尝试找到 Model 类型的参数。
    • 获取 AddCustomModelAttr 注解的 key 和 value 属性。
    • 使用 model.addAttribute(key, value) 将数据添加到 Model 中。

获取 Model 对象的注意事项: 在Web环境中,Model 对象通常作为 Controller 方法的参数由Spring自动注入。上述切面通过遍历 JoinPoint 的参数来获取 Model。如果目标方法没有 Model 参数,或者 Model 是通过其他方式(如 ModelAndView 返回值)处理的,那么上述获取 Model 的逻辑可能需要调整。对于更复杂的场景,可能需要使用 RequestContextHolder 获取当前请求,然后通过 HttpServletRequest 或 HttpServletResponse 来间接操作,或者将通知类型改为 @Around 并处理 ModelAndView 返回值。

4. 应用自定义注解到Controller

现在,我们可以在Spring Boot的Controller方法或类上使用这个自定义注解了。

package com.example.demo.controller;

import com.example.demo.annotation.AddCustomModelAttr;
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
@RequestMapping("/ex")
public class ExController {

    // 示例1:在方法上使用注解,使用默认的key和value
    @AddCustomModelAttr
    @GetMapping("/index1")
    public String index1(Model model){
        System.out.println("Controller: index1 方法执行,Model内容:" + model.asMap());
        return "index"; // 假设存在一个名为 index.html 的Thymeleaf模板
    }

    // 示例2:在方法上使用注解,并指定自定义的key和value
    @AddCustomModelAttr(key = "customKey", value = "customValue")
    @GetMapping("/index2")
    public String index2(Model model){
        System.out.println("Controller: index2 方法执行,Model内容:" + model.asMap());
        return "index";
    }

    // 示例3:在类上使用注解,所有方法都将继承该逻辑
    // 为了演示,我们将这个注解移到类级别,并创建一个新的Controller
}

@AddCustomModelAttr(key = "classLevelKey", value = "classLevelValue")
@Controller
@RequestMapping("/class-level")
class ClassLevelAnnotatedController {

    @GetMapping("/home")
    public String home(Model model) {
        System.out.println("Controller: home 方法执行,Model内容:" + model.asMap());
        return "index";
    }

    @GetMapping("/about")
    public String about(Model model) {
        System.out.println("Controller: about 方法执行,Model内容:" + model.asMap());
        return "index";
    }
}

Controller说明:

百度智能云·曦灵
百度智能云·曦灵

百度旗下的AI数字人平台

下载
  • index1() 方法被 @AddCustomModelAttr 标记,但没有指定属性,因此会使用注解的默认值(key="defaultKey", value="defaultValue")。
  • index2() 方法被 @AddCustomModelAttr(key = "customKey", value = "customValue") 标记,将使用指定的键值对。
  • ClassLevelAnnotatedController 整个类被 @AddCustomModelAttr(key = "classLevelKey", value = "classLevelValue") 标记,其内部的所有方法(如 home() 和 about())都将自动应用该注解的逻辑。

5. 启用AOP代理

在Spring Boot应用中,通常无需手动配置,Spring Boot会自动检测到类路径下的AspectJ库和 @Aspect 注解,并自动启用AOP代理。如果你的项目没有自动启用AOP,你可以在主应用类上添加 @EnableAspectJAutoProxy 注解。

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy // 如果AOP没有自动启用,则需要添加此注解
public class DemoApplication {

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

}

6. 运行与验证

启动Spring Boot应用,并访问 /ex/index1、/ex/index2、/class-level/home 或 /class-level/about 等URL。你会在控制台看到切面输出的日志,以及Controller方法中打印的 Model 内容,验证自定义属性是否已成功添加。

例如,访问 /ex/index1 后,控制台可能会输出:

AOP: 成功向Model中添加属性 - key: defaultKey, value: defaultValue
Controller: index1 方法执行,Model内容:{defaultKey=defaultValue}

这表明,在 index1 方法实际执行之前,切面已经介入并向 Model 中添加了 defaultKey:defaultValue。

7. 总结与注意事项

通过自定义注解和Spring AOP,我们成功实现了在不修改业务方法代码的前提下,动态地向方法中注入特定逻辑。这种方式极大地提高了代码的模块化和复用性。

注意事项:

  1. AOP代理类型: Spring AOP默认使用JDK动态代理来代理接口,如果目标类没有实现接口,则会使用CGLIB代理。确保你的环境支持CGLIB(Spring Boot通常默认包含)。
  2. Model 获取: 示例中假设 Model 作为方法参数传入。如果你的Controller方法不接收 Model 参数,或者需要操作 ModelAndView 返回值,你需要调整切面逻辑以正确获取和操作 Model。例如,使用 @Around 通知来拦截方法的整个执行过程,并处理 ModelAndView。
  3. 切点表达式: 精确的切点表达式是关键。@annotation() 用于方法级别的注解,@within() 用于类级别的注解。根据实际需求选择合适的切点。
  4. 性能考虑: AOP会增加方法调用的开销,对于性能敏感的场景,应谨慎使用或优化切面逻辑。
  5. 异常处理: 在实际应用中,切面内部的逻辑也可能抛出异常。可以考虑使用 @AfterThrowing 通知来处理这些异常,或者在 @Around 通知中进行 try-catch。
  6. 复杂逻辑: 对于更复杂的逻辑,可以将通知方法中的逻辑进一步封装到独立的Service类中,保持切面的简洁性。

这种模式不仅限于向 Model 添加属性,还可以用于实现权限校验、数据审计、缓存管理、事务控制等各种横切关注点,是Spring框架中一个非常强大且常用的特性。

相关专题

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

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

102

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 应用的流行工具。

32

2025.12.22

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

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

114

2025.12.24

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1017

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

62

2025.10.17

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

36

2026.01.14

热门下载

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

精品课程

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

共23课时 | 2.5万人学习

C# 教程
C# 教程

共94课时 | 6.7万人学习

Java 教程
Java 教程

共578课时 | 45.9万人学习

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

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