Spring Boo项目中方法参数对象中字段上存在的自定义注解如何进行拦截解析

news2025/2/27 14:32:14

一、前言

在Spring Boot项目开发过程中,我们经常会使用到自定义注解的方式进行业务逻辑开发,此时注解我们一般是放在方法或者类上面,通过AOP切面拦截的方式进行自定义业务逻辑填充。但是如果自定义注解放在类的字段上,此时应该如何进行解析呢?

二、技术思路

自定义注解放在类的字段上其实解析起来也是比较容易的,我这里采用的思路是:

自定义一个MethodInterceptor拦截器拦截执行的业务方法,获取到方法参数数组,遍历每个参数,获取每个参数中所有字段,判断字段上是否有自定义注解,筛选出所有标识了自定义注解的字段,然后进行自定义业务逻辑填充。

三、技术实践

按照上面的思路,我们进行一下技术实践,这里我模拟一个案例。

1. 需求

自定义一个注解,该注解作用于类的字段上,带有该标识注解的字段,在使用时,如果字段没有设置值,则采用注解设置的默认值。

2. 新建spring boot项目,导入相关依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3. 自定义注解

示例代码如下:

import java.lang.annotation.*;

@Target({ElementType.FIELD}) // 标识此注解适用于字段上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DefaultValueAnnotation {

    String value() default "";
}

4. 字段注解获取工具类

示例代码:

import lombok.Data;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * 字段上注解查找工具类
 */
public class FieldAnnotationUtils {

    /**
     * 字段注解信息返回VO
     */
    @Data
    public static class FieldAnnotationInfo {
        /**
         * 类变量
         */
        private Object obj;
        /**
         * 携带该注解的字段对象
         */
        private Field field;
        /**
         * 注解对象
         */
        private Annotation annotation;
    }

    /**
     * 判断类变量中是否字段上存在指定的注解,如果存在,则返回字段和注解信息
     *
     * @param obj            类变量
     * @param annotationType 注解类型
     * @return
     */
    public static List<FieldAnnotationInfo> parseFieldAnnotationInfo(Object obj, Class annotationType) {
        return parseFieldAnnotationInfo(obj, annotationType, null);
    }

    /**
     * 判断类变量中是否字段上存在指定的注解,如果存在,则返回字段和注解信息
     *
     * @param obj              类变量
     * @param annotationType   注解类型
     * @param filterFieldClazz 注解适用的字段类型(不适用的字段类型即使字段上面添加改注解也不生效)
     * @return
     */
    public static List<FieldAnnotationInfo> parseFieldAnnotationInfo(Object obj, Class annotationType, Set<Class> filterFieldClazz) {
        if (obj == null) {
            return null;
        }
        List<FieldAnnotationInfo> resultList = new ArrayList<>();
        /**
         * 获取该对象的所有字段,进行遍历判断
         */
        ReflectionUtils.doWithFields(obj.getClass(), field -> {
            // 判断该字段上是否存在注解
            Annotation annotation = AnnotatedElementUtils.findMergedAnnotation(field, annotationType);
            if (annotation != null) { // 如果存在指定注解
                boolean flag = true;
                if (filterFieldClazz != null && !filterFieldClazz.isEmpty()) { // 如果指定了适用的字段的类型
                    for (Class c : filterFieldClazz) {
                        if (c.isAssignableFrom(field.getType())) { // 判断该字段类型是否符合使用类型,使用isAssignableFrom方法是为了父类也进行判断
                            break;
                        }
                        flag = false;
                    }
                }
                if (flag) { // 如果该字段类型符合,则返回字段注解信息
                    FieldAnnotationInfo fieldAnnotationInfo = new FieldAnnotationInfo();
                    fieldAnnotationInfo.setObj(obj);
                    fieldAnnotationInfo.setField(field);
                    fieldAnnotationInfo.setAnnotation(annotation);
                    resultList.add(fieldAnnotationInfo);
                }
            }
        });
        return resultList;
    }
}

5. 自定义MethodInterceptor

示例代码:

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 获取执行方法的所有参数
        Object[] arguments = invocation.getArguments();
        // 参数列表不为空
        if (arguments != null && arguments.length != 0) {

            // 由于我这里注解定义简单,我只让String类型的字段生效
            Set<Class> clazzSet = new HashSet<>();
            clazzSet.add(CharSequence.class);

            for (int i = 0; i < arguments.length; i++) { // 判断所有参数
                // 从每个参数中获取字段上有@DefaultValueAnnotation注解标识,并且类型是CharSequence的字段信息
                List<FieldAnnotationUtils.FieldAnnotationInfo> fieldAnnotationInfos = FieldAnnotationUtils.parseFieldAnnotationInfo(arguments[i], DefaultValueAnnotation.class, clazzSet);
                if (!CollectionUtils.isEmpty(fieldAnnotationInfos)) { // 如果存在符合条件的字段信息
                    for (int m = 0; m < fieldAnnotationInfos.size(); m++) { // 判断所有符合条件的字段
                        FieldAnnotationUtils.FieldAnnotationInfo fni = fieldAnnotationInfos.get(m);
                        Field field = fni.getField(); // 获取字段
                        field.setAccessible(true); // 设置可访问,突破private权限
                        Object value = field.get(fni.getObj());
                        if (value == null) { // 判断当前字段是否已经有值
                            // 如果当前字段没有值,利用反射的方式,把注解中的值设置进去
                            field.set(fni.getObj(), ((DefaultValueAnnotation) fni.getAnnotation()).value());
                        }
                        field.setAccessible(false);
                    }
                }
            }
        }
        // 放行方法执行
        return invocation.proceed();
    }
}

6. 把自定义的MethodInterceptor织入到Spring容器中

示例代码:

import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyInterceptorConfig {

    @Bean
    public DefaultPointcutAdvisor DefaultPointcutAdvisor() {
        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
        defaultPointcutAdvisor.setAdvice(new MyMethodInterceptor()); // 自定义MethodInterceptor

        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression("execution(* com.learn..*(..))"); // 指定需要拦截的切面

        defaultPointcutAdvisor.setPointcut(aspectJExpressionPointcut);
        return defaultPointcutAdvisor;
    }

}

7. 使用自定义注解

示例代码:

import lombok.Data;

@Data
public class TestParamPojo {

    @DefaultValueAnnotation("我是注解设置的值.")
    private String name;

    @DefaultValueAnnotation("100")
    private Integer age;
}

8. 定义方法入参

示例代码:

@Component
public class TestService {

    public void test() {
        System.out.println("test empty...");
    }

    public void test(TestParamPojo pojo) {
        System.out.println("test pojo: " + pojo.toString());
    }

    public void test(TestParamPojo pojo, String testStr) {
        System.out.println("test pojo: " + pojo.toString() + " testStr: " + testStr);
    }

}

9.测试

  • 测试代码:

        @Autowired
        private TestService testService;
    
        @PostConstruct
        public void init() {
            System.out.println("=========================");
            testService.test();
            TestParamPojo testParamPojo = new TestParamPojo();
            testService.test(testParamPojo);
            testService.test(testParamPojo, null);
            testParamPojo.setName("我是自己设置的...");
            testService.test(testParamPojo, "hello, world.");
            System.out.println("=========================");
        }
    
  • 测试结果:

    在这里插入图片描述

  • 测试结论

    可以看到,在没有设置值的时候,可以从注解中获取默认值进行设置,同时,不是指定的字段类型即使标识了自定义注解也不会生效。

四、写在最后

本文是对Spring Boo项目中方法参数对象中字段上存在的注解如何进行拦截解析的技术探讨,以上只是技术思路的一种实现方式,仅供大家参考。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Power Apps-组件使用全局变量

组件并不可以直接使用全局变量&#xff0c;若想使用全局变量可以使用如下方法&#xff1a; 首先点击整个组件&#xff0c;在右侧边栏中找到自定义属性&#xff0c;点击添加新的自定义属性 填写相关信息&#xff0c;显示名称和名称填写全局变量名称&#xff0c;说明随意编辑 再…

MATLAB环境下基于距离正则化水平集演化方法的图像分割

水平集图像分割方法实际上是基于曲线演化理论的一种数学方法在图像上的表示&#xff0c;能够处理曲线的拓扑结构变化&#xff0c;而且其数值实现的稳定性高,医学超声图像由于其成像原理一般会具有较高的斑点噪声并且一些区域存在对比度低的情况。传统的图像分割算法并不能在医学…

租赁小程序|租赁系统|租赁软件开发带来高效运营

随着社会的不断发展和科技的不断进步&#xff0c;越来越多的企业开始关注设备租赁业务。设备租赁作为一种短期使用设备的方式&#xff0c;为企业提供了灵活和成本节约的优势。针对设备租赁业务的管理和提升企业竞争力的需求&#xff0c;很多企业选择定制开发设备租赁系统。本文…

【每日一题】938. 二叉搜索树的范围和-2024.2.26

题目&#xff1a; 938. 二叉搜索树的范围和 给定二叉搜索树的根结点 root&#xff0c;返回值位于范围 [low, high] 之间的所有结点的值的和。 示例 1&#xff1a; 输入&#xff1a;root [10,5,15,3,7,null,18], low 7, high 15 输出&#xff1a;32示例 2&#xff1a; 输入…

ESP32(VSCode+PlatformIO)开发环境搭建教程(2024版)

目录 一、安装vscode&#xff1a;[点击下载](https://code.visualstudio.com/Download)二、安装Python环境三、安装VSCode platformio插件四、使用PlatformIO创建项目五、编译下载 一、安装vscode&#xff1a;点击下载 二、安装Python环境 本文以Win11系统做演示&#xff0c;其…

Unity(第六部)向量的理解和算法

标量:只有大小的量。185 888 999 &#xff08;类似坐标&#xff09; 向量:既有大小&#xff0c;也有方向。&#xff08;类似以个体为主体的方向&#xff0c;前方一百米&#xff09; 向量的模:向量的大小。&#xff08;类似以个体为主体的方向&#xff0c;前方一百米、只取一百米…

配置用户自动获取IPv6地址的案例

知识改变命运&#xff0c;技术就是要分享&#xff0c;有问题随时联系&#xff0c;免费答疑&#xff0c;欢迎联系&#xff01; ​​​​​​https://www.xmws.cn华为认证\华为HCIA-Datacom\华为HCIP-Datacom\华为HCIE-DatacomLinux\RHCE\RHCE 9.0\RHCA\ Oracle OCP\CKA\K8S\ CIS…

蓝桥杯-最小砝码

知识点&#xff1a;本题主要考察任何一个物体都可以用 3进制表示。 #include <iostream> #include<cmath> using namespace std; //知识点:任何一个物体都可以用 3进制表示 int main() { int n; cin >> n; int sum 0; for (int i 0;; i)…

初学学习408之数据结构--数据结构基本概念

初学学习408之数据结构我们先来了解一下数据结构的基本概念。 数据结构&#xff1a;是相互之间存在一种或多种特定关系的数据元素的集合。 本内容来源于参考书籍《大话数据结构》与《王道数据结构》。除去书籍中的内容&#xff0c;作为初学者的我会尽力详细直白地介绍数据结构的…

【Prometheus】概念和工作原理介绍

目录 一、概述 1.1 prometheus简介 1.2 prometheus特点 1.3 prometheus架构图 1.4 prometheus组件介绍 1、Prometheus Server 2、Client Library 3、pushgateway 4、Exporters 5、Service Discovery 6、Alertmanager 7、grafana 1.5 Prometheus 数据流向 1.6 Pro…

liunx前后端分离项目部署

文章目录 1、nginx的安装和自启动2.nginx负载均衡3.前后端项目部署-后端部署4.前后端项目部署-前端部署 1、nginx的安装和自启动 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel1.安装我们nginx所需要的依赖 wget http://nginx.org/download/nginx-1.…

S32 Design Studio PE工具配置TMR

配置步骤 配置内容 生成的配置结构体如下&#xff0c;在Generated_Code路径下的lpTmr.c文件和lpTmr.h文件。 /*! lpTmr1 configuration structure */ const lptmr_config_t lpTmr1_config0 {.workMode LPTMR_WORKMODE_PULSECOUNTER,.dmaRequest false,.interruptEnable tr…

《高质量的C/C++编程规范》学习

目录 一、编程规范基础知识 1、头文件 2、程序的板式风格 3、命名规则 二、表达式和基本语句 1、运算符的优先级 2、复合表达式 3、if语句 4、循环语句的效率 5、for循环语句 6、switch语句 三、常量 1、#define和const比较 2、常量定义规则 四、函数设计 1、参…

npm i卡在 idealTree buildDeps没反应的解决方案

通过git clone拉下项目后&#xff0c;进行项目的初始化下包时&#xff0c;发现npm i 并没有反应&#xff08;如图&#xff09;&#xff1a; 关键点&#xff1a;IdealTree 1.网络问题 确保你的网络连接正常&#xff0c;能够正常访问 npm 仓库。有时网络问题可能导致包无法正确…

GitHub下载器,老司机懂的都懂!

有些老司机或者做项目的小伙伴对GitHub应该不陌生吧&#xff0c;然而GitHub的下载速度非常让人不忍直视&#xff01; 而GitHub高速下载器是一款专门用于加速在GitHub上下载资源的软件&#xff0c;解决了许多用户在下载GitHub资源时遭遇的速度慢和下载失败的问题。 本教程将详细…

代码随想录算法训练营第62天 | 739.每日温度 496.下一个更大元素I

每日温度 如果我们单纯的遍历数组&#xff0c;我们不知道当前元素是否比之前的元素大&#xff0c;所以需要维护一个容器来记录遍历过的元素。 什么时候用单调栈&#xff1f;通常是一维数组&#xff0c;要寻找任一个元素的右边或左边第一个比自己大或小的元素的位置。时间复杂度…

Connection管理类实现(模块六)

目录 类功能 类定义 类实现 编译 本文使用了自定的Any类 Any类的简单实现-CSDN博客 类功能 类定义 // DISCONECTED -- 连接关闭状态 CONNECTING -- 连接建立成功-待处理状态 // CONNECTED -- 连接建立完成,各种设置已完成,可以通信状态 DISCONNECTING -- 待关闭状态 t…

每日五道java面试题之spring篇(六)

目录&#xff1a; 第一题 ApplicationContext通常的实现是什么&#xff1f;第二题 什么是Spring的依赖注入&#xff1f;第三题 依赖注入的基本原则第四题 依赖注入有什么优势&#xff1f;第五题 有哪些不同类型的依赖注入实现方式&#xff1f; 第一题 ApplicationContext通常的…

基于频率增强的数据增广的视觉语言导航方法(VLN论文阅读)

基于频率增强的数据增广的视觉语言导航方法&#xff08;VLN论文阅读&#xff09; 摘要 视觉和语言导航&#xff08;VLN&#xff09;是一项具有挑战性的任务&#xff0c;它需要代理基于自然语言指令在复杂的环境中导航。 在视觉语言导航任务中&#xff0c;之前的研究主要是在空间…

pycharm如何设置滚轮缩放代码大小?

左上角的File找到设置&#xff0c;或者快捷键ctrlalts。 弹出对话框&#xff0c;手动输入mouse&#xff0c;点击general&#xff0c;勾选改变字体大小&#xff0c;ok确认