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

一、前言

在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项目中方法参数对象中字段上存在的注解如何进行拦截解析的技术探讨,以上只是技术思路的一种实现方式,仅供大家参考。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言中strstr函数的使用!

strstr函数的作用是什么&#xff1f; 查找子字符串 具体直接看下面的这段代码我相信你必明白 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { char *p1 "abcdefghijklmnopqrstuvwxyz"; char* p2 "abc"; char* r…

SocketWeb实现小小聊天室

SocketWeb实现小小聊天室 消息推送的常见方式轮询长轮询SSE&#xff08;server-sent event&#xff09;&#xff1a;服务器发送事件WebSocketWebSocket简介WebSocket API 实现小小聊天室实现流程消息格式客户端-->服务端服务端-->客户端 消息推送的常见方式 轮询 浏览器…

c语言经典测试题4

1.题1 #include <stdio.h>//没有break的话&#xff0c;输入什么都会往下一直执行下去&#xff0c;而且default在最后就会全都执行 int main() {char c;int v0 0, v1 0, v2 0;do{switch (c getchar())// 输入ADescriptor{casea:caseA:casee:caseE:casei:caseI:caseo:…

sklearn.preprocessing.RobustScaler(解释和原理,分位数,四分位差)

提示&#xff1a;sklearn.preprocessing.RobustScaler&#xff08;解释和原理&#xff0c;分位数&#xff0c;四分位差&#xff09; 文章目录 [TOC](文章目录) 一、RobustScaler 是什么&#xff1f;二、代码1.代码2.输出结果 总结 提示&#xff1a;以下是本篇文章正文内容&…

数据结构2月21日

双向链表: func函数&#xff1a; #include <stdio.h> #include <stdlib.h> …

人事|人事管理系统|基于Springboot的人事管理系统设计与实现(源码+数据库+文档)

人事管理系统目录 目录 基于Springboot的人事管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员登录 2、员工管理 3、公告信息管理 4、公告类型管理 5、培训管理 6、培训类型管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、…

AI一键生成3D模型!

一、Genie Genie 是 Luma AI 推出的一个文本到 3D 的生成模型&#xff0c;可以在 10 秒生成 4 款 3D 模型&#xff0c;自动精修后质感非常逼真&#xff0c;目前支持免费使用。 此次的 1.0 版本更新后将生成功能由 Discord 转到了单独的网页&#xff0c;使用起来更方便&#x…

C# 学习第三弹——表达式

表达式操作数运算符 &#xff08;一&#xff09;算数运算符 错误例子&#xff1a;这不是python&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 正确结果&a…

安全防御综合实验

需求&#xff1a; 1、办公区设备可以通过电信链路和移动链路上网&#xff08;多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换&#xff09; 2、分公司设备可以通过总公司的移动链路和电信链路访问DMZ区的http服务器 3、分公司内部的客户端可以通过公网地址访问到…

《数据安全销毁实践指南》:企业文件数据保密销毁之人员能力流程技术等干货

数据销毁处理 为了满足合规要求及组织机构本身的业务发展需求&#xff0c;组织机构需要对数据进行销毁处理。因为数据销毁处理要求针对数据的内容进行清除和净化&#xff0c;以确保攻击者无法通过存储介质中的数据内容进行恶意恢复&#xff0c;从而造成严重的敏感信息泄露问题。…

NVM存储设备MTBF介绍

1. 概念 1.1. MTBF MTBF(Mean Time Between Failure)&#xff0c;平均故障间隔时间&#xff0c;也被称为平均无故障时间&#xff0c;是衡量一个产品的可靠性指标&#xff0c;其单位为小时。其定义为&#xff1a;产品在总的使用阶段累计工作时间与故障次数的比值&#xff1a; …

小技巧:Nuxt处理全局组件的显示与隐藏

在Nuxt开发过程中&#xff0c;大家会遇到需要控制全局组件的显示与隐藏。比如说移动端的路由导航栏、头部的返回操作。。。 为了使切换页面的同时&#xff0c;确定是否展示全局组件&#xff0c;保证页面展示的平稳过渡。 下面是我在项目中用到一个办法&#xff0c;已实现。 …

STL常用容器(vector容器)---C++

STL常用容器目录 2.vector容器2.1 vector基本概念2.2 vector构造函数2.3 vector赋值操作2.4 vector容量和大小2.5 vector插入和删除2.6 vector数据存取2.7 vector互换容器2.7.1 vector互换容器收缩内存空间 2.8 vector预留空间 2.vector容器 2.1 vector基本概念 功能&#xf…

FL Studio Producer Edition2024中文进阶版Win/Mac

FL Studio Producer Edition&#xff0c;特别是其【中文进阶版 Win/Mac】&#xff0c;是数字音乐制作领域中的一款知名软件。它为广大音乐制作人、声音工程师以及音乐爱好者提供了一个从音乐构思到最终作品发布的完整解决方案。这个版本特别为中文用户优化&#xff0c;并兼容W…

IT廉连看——C语言——概述

IT廉连看——C语言概述 一、什么是c语言 C语言是一门通用计算机编程语言&#xff0c;广泛应用于底层开发。C语言的设计目标是提供一种能以简易 的方式编译、处理低级存储器、产 生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。 尽管C语言提供了许多低级处理的功…

彩信群发优势及应用场景分析!

一、彩信群发产品服务 彩信群发为多媒体信息服务&#xff0c;通常又称为彩信广告或者彩信营销。其发送与接收一般采用“点对点”的发送方式&#xff0c;系统组成包括软件和硬件设备&#xff08;GPRS MODEM&#xff09;&#xff0c;企业可通过系统&#xff0c;如有关企业形象、产…

电商风控系统(flink+groovy+flume+kafka+redis+clickhouse+mysql)

一.项目概览 电商的防止薅羊毛的风控系统 需要使用 groovy 进行风控规则引擎的编写 然后其它技术进行各种数据的 存储及处理 薅羊毛大致流程 如果单纯使用 if else在业务代码中进行风控规则的编写 那么 维护起来会比较麻烦 并且跟业务系统强绑定不合适 所以一般独立成一个单…

选择排序的简单介绍

选择排序是一种简单直观的排序算法&#xff0c;其原理如下&#xff1a; 1. 遍历数组&#xff0c;找到最小&#xff08;或最大&#xff09;的元素&#xff0c;并将其与数组的第一个元素交换位置。 2. 接着在剩下的元素中找到最小&#xff08;或最大&#xff09;的元素&#xff…

前端架构: 脚手架命令行交互核心实现之inquirer的应用教程

命令行交互核心实现 核心目标&#xff1a;实现命令行行交互&#xff0c;如List命令行的交互呢比命令行的渲难度要更大&#xff0c;因为它涉及的技术点会会更多它涉及以下技术点 键盘输入的一个监听 (这里通过 readline来实现)计算命令行窗口的尺寸清屏光标的移动输出流的静默 …

术中导航系统开发:助力医疗机构提升手术效率和安全性

一、市场概况 近年来&#xff0c;随着医学影像技术和计算机技术的快速发展&#xff0c;术中导航系统得到了广泛应用。根据QY Research的报告&#xff0c;2022年全球术中导航系统市场规模达到了15亿美元&#xff0c;预计到2028年将达到28亿美元。 二、技术特点 术中导航系统主…