SpringBoot通过自定义字段注解以及反射获取对象

2023-10-27

在Java的开发过程中,注解的应用场景是非常广泛的。Java也提供了很多内置的注解,比如@Override,@Deprecated,@SuppressWarnings等等。之前也写过一篇注解相关的文章,SpringBoot自定义注解 AOP以及拦截器方式。本文主要介绍通过自定义字段注解以及反射,实现初始化对象的功能。应用场景主要是通过外部接口,数据库,文本或者Excel读取数据,然后通过反射以及字段注解自动转换为对象,灵活的处理外部数据到对象的转换。

自定义字段注解

定义自定义字段注解DbFieldProperty,value表示字段注解的值,name为字段注解名称,clazz为类型。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DbFieldProperty {
    String value() default "";

    String name() default "";

    Class clazz() default Object.class;
}

使用自定义注解

在AutoUser类中使用我们自定义的字段注解,每个字段与数据库中flyduck_user表相对应。注解中的value设置为数据库表中的列名。

public class AutoUser {
    @DbFieldProperty(value = "id", name = "id", clazz = String.class)
    private String id;

    @DbFieldProperty(value = "user_name", name = "用户名")
    private String userName;

    @DbFieldProperty(value = "password", name = "密码")
    private String password;

    @DbFieldProperty(value = "phone_number", name = "手机号")
    private String phoneNumber;

    @DbFieldProperty(value = "real_name", name = "姓名")
    private String realName;

    @DbFieldProperty(value = "email", name = "邮箱")
    private String email;

    @DbFieldProperty(value = "status", name = "状态", clazz = Integer.class)
    private Integer status;

    @DbFieldProperty(value = "is_delete", name = "删除")
    private Integer delete;

    @DbFieldProperty(value = "create_date", name = "创建时间", clazz = Date.class)
    private Date createDate;

    @DbFieldProperty(value = "update_date", name = "更新时间")
    private Date updateDate;

    // 省略 getter setter 代码

}

数据转换为对象

这里我们使用原始的JDBC方式从数据库表flyduck_user中获取数据,然后将通过执行SQL查询语句获得的数据集ResultSet转换为我们定义的AutoUser对象列表。

    @GetMapping("list")
    public String listUser() {
        String result = "";

        try {
            Class.forName(webConfig.getDbDriver());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        try (Connection c = DriverManager.getConnection(webConfig.getDbUrl(), webConfig.getDbUsername(), webConfig.getDbPassword());
             Statement s = c.createStatement();) {

            String sql = "select * from flyduck_user limit 10";

            Map<String, Map<String, Object>> fieldMap = getFieldMap(AutoUser.class);


            // 执行查询语句,并把结果集返回给ResultSet
            ResultSet rs = s.executeQuery(sql);
            ResultSetMetaData resultSetMetaData = rs.getMetaData();
            List<Map<String, Object>> list = new ArrayList<>();
            List<AutoUser> userList = new ArrayList<>();
            while (rs.next()) {
                Map<String, Object> row = new HashMap<>();
                for (int index = 1; index <= resultSetMetaData.getColumnCount(); index++) {
                    row.put(resultSetMetaData.getColumnName(index).toLowerCase(), rs.getObject(index));
                }
                list.add(row);

                AutoUser user = buildObject(AutoUser.class, row, fieldMap);
                userList.add(user);
            }

            result = objectMapper.writeValueAsString(userList);

        } catch (Exception e) {
            // TODO 异常处理
        }

        return result;
    }

通过getFieldMap方法从类中将自定义字段注解的字段通过反射获取出来。

    private Map<String, Map<String, Object>> getFieldMap(Class clazz) {
        List<Field> tempFieldList = new ArrayList();

        for(Class tempClass = clazz; tempClass != null; tempClass = tempClass.getSuperclass()) {
            Collections.addAll(tempFieldList, tempClass.getDeclaredFields());
        }

        Map<String, Map<String, Object>> fieldMap = new HashMap<>();

        for (Field field : tempFieldList) {
            Map<String, Object> propertyMap = new HashMap<>();
            String fieldValue = field.getName().toLowerCase();

            propertyMap.put(KEY_PROPERTY, field.getName());

            DbFieldProperty fieldProperty = field.getAnnotation(DbFieldProperty.class);
            if (fieldProperty != null) {
                if (!StringUtils.isEmpty(fieldProperty.value())) {
                    fieldValue = fieldProperty.value().toLowerCase();
                }
                propertyMap.put(KEY_NAME, fieldProperty.name());
                propertyMap.put(KEY_TYPE, fieldProperty.clazz());
            }
            propertyMap.put(KEY_FIELD, fieldValue);

            fieldMap.put(fieldValue, propertyMap);
        }

        return fieldMap;
    }

通过buildObject方法,将从数据库表获取的数据按照自定义注解的方式转换初始化为对象。这个方法主要思路是按照类的字段注解提取数据放入map中,key为类的属性名。然后通过clazz.newInstance()创建对象,通过BeanMap.create(resultModel).putAll(map)给新创建的对象赋值。

    private <T> T buildObject(Class clazz, Map<String, Object> data, Map<String, Map<String, Object>> fieldMap) throws IllegalAccessException, InstantiationException {
        Map<String, Object> map = new HashMap();
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            Map<String, Object> propertyMap = fieldMap.get(entry.getKey());
            if (propertyMap != null) {
                Object value = entry.getValue();
                Class propertyClazz = (Class) propertyMap.get(KEY_TYPE);
                if (value != null && propertyClazz != null && !propertyClazz.equals(Object.class)) {
                    Object finalValue = propertyClazz.cast(value);
                    map.put(propertyMap.get(KEY_PROPERTY).toString(), finalValue);
                } else {
                    map.put(propertyMap.get(KEY_PROPERTY).toString(), value);
                }
            }
        }
        Object resultModel = clazz.newInstance();
        BeanMap.create(resultModel).putAll(map);
        return (T) resultModel;
    }

测试

我们这里的示例是在接口中,所以可以很方便的通过单元测试看下是否转换成功。

    @Test
    public void listUserTest() throws Exception {
        String result = mock
                .perform(get("/user/list")
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                )
                .andExpect(status().isOk())
                .andDo(print())
                .andReturn()
                .getResponse()
                .getContentAsString();

        assert !StringUtils.isEmpty(result) : "获取用户列表接口未通过测试";
    }

 单元测试通过,通过JDBC从数据库表中获取的数据成功转换并赋值给用户对象列表。

 总结

在实际项目中有很多类似的需求,如果是通过接口获取的json格式的数据,可以很容易的通过Json的包反序列化为需要的对象。但是类似于从Excel或者数据库表获取的数据,不是键值格式的数据,而是结构(列名)与数据分开的,我们可以通过自定义字段注解以及反射的方式转换为对象。本文仅仅从应用层面介绍了通过自定义字段注解和反射将数据转换为对象,未做进一步深入的研究,如果有描述错误的地方欢迎各位博友指正。

示例的源码已经上传到Gitee: https://gitee.com/flyduck128/springboot-demo/tree/master/flyduck-annotation

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot通过自定义字段注解以及反射获取对象 的相关文章

随机推荐

  • Spring Boot整合Mybatis-Plus快速入门(二)

    之前快速搭建项目中 创建的类名与表名以及属性名都是一致的 所以mybatis plus可以根据类名以及属性名进行对应的操作 那么如果表名与属性名中不一致或属性名与列名不一致时我们应该怎么处理呢 这里mybatis plus为我们提供了很多注
  • 解决VMware14打开虚拟机黑屏问题

    最近把VMware升级到14 0之后 运行虚拟机之后无法显示图像 屏幕一团黑 但点击显示缩略图的时候是可以看到系统已经启动并在正常运行的 可以通过以下方法解决 以管理员身份打开命令提示符 输入命令 netsh winsock reset 重
  • 知识图谱(七)——事件抽取

    文章目录 一 任务概述 1 事件的定义 2 事件抽取的定义 3 相关评测和语料资源 二 限定域事件抽取 1 基于模式匹配的事件抽取方法 1 有监督的事件模式匹配 2 弱监督的事件模式匹配 3 优缺点 2 基于机器学习的事件抽取方法 1 有监
  • arm-none-eabi-gcc编译、链接选项详解

    1 mthumb 和 mthumb interwork mthumb 的意义是 使用这个编译选项生成的目标文件是Thumb指令的 目前还没有发现GNU编译器中有哪一个选项可以指定生成的目标文件是thumb 2的 相对应的 marm 的意义是
  • 多态,虚函数,纯虚函数

    多态 借助虚函数 基类指针既可以使用基类 父类 的成员函数 也可以使用派生类 子类 的成员函数 它有多种形态 或多种表现方式 这就是多态 简单说就是同一条语句可以执行不同的操作 看起来有不同表现方式 这就是多态 构成多态的条件 多态存在的三
  • .net 平台下的数学库math.net(一)

    Math NET的目标是为提供一款自身包含清晰框架的符号运算和数学运算 科学运算 它是C 开发的开源类库 Math NET含了一个支持线性代数的解析器 分析复杂微分 解方程等等功能 这个项目大部分采用的是MIT X11开源软件协议 目前该组
  • ConstraintLayout实用特性

    转载自赵彦军的博客 前言 在2016年的Google I O大会上 Google 发布了Android Studio 2 2预览版 同时也发布了Android 新的布局方案 ConstraintLayout 但是最近的一年也没有大规模的使用
  • 【ABviewer从零开始教学查看器篇②】关于打开文件的快捷方式

    ABViewer是一款高质量 高效率 低成本的多功能设计及工程文档管理工具 能为您提供全面的专业的浏览及编辑功能 同时支持30多种光栅和矢量图形格式 在小编看来 ABViewer是一款非常简单且实用的CAD文档查看与编辑器 对于使用小白可能
  • 判断是否是数组

    整理了一些 留待自己复习用 1 instanceof var a name fangxiaoming age 19 var b 1 2 3 4 console log a instanceof Array false console log
  • 【翻译】我们建立了一个.NET操作员SDK(所以您不必这样做)。

    我们用C 语言构建了一个 NET操作者SDK 因此您可以用C 或任何 NET语言构建自己的Kubernetes操作者 当然也 有Go Operator SDK 还有我们的Java Operator SDK 那么为什么不为 NET社区提供一些
  • 用 Visual Studio 2019 编译 FFmpeg 简单教程

    需要的东西 Visual Studio 2019 这个自行解决吧 本人用的是社区版 MSYS 环境 去 https www msys2 org 下载 本人下载的是 msys2 x86 64 20210725 exe yasm exe 去 h
  • Security in IP-Based IoT Node and Device Authentication

    Abstract The IoT security aims for enabling IoT data protection in various interconnected nodes These frameworks require
  • 触屏fling/scroll/drag的区别及其详细过程

    原文地址 Android 触屏fling scroll drag的区别及其详细过程 作者 飘锦丹枫 Google了一下 终于搞清了touch screen下的几种操作模式 对应的是事件 对于一个view 常用的操作有点击 click 和长按
  • 原生js实现ajax请求(带请求头header)和数据传参过程代码

    一 Ajax 概述 Ajax 是 Asynchronous Javascript And XML 的简写 Ajax是一门技术 并不是一门语言 使用XHTML CSS来标准化呈现 使用XML和XSLT进行数据交换及相关操作 使用XMLHttp
  • java程序输出中文乱码解决方案

    标题java程序输出中文乱码解决方案 乱码如下 你好 在一些Java程序中我们输入的中文在输出时会出现乱码的情况 一下是解决方案 1 在编译xx java文件时使用javac encoding utf 8 xx java语句进行编译可以解决
  • sed 过滤字符文本 (一行行的)

    前面写过用sed对整个文件过滤的 代码很简单 现在这个是能够取出其中的一行行来过滤的 为了获取更多的相关信息 注意列表中的空格先变为 然后再变回来 不然会出错 bin sh i grep chenbing my c temp sed s g
  • 关于代码评审

    总结于 代码之丑 专栏 郑晔 为何要做代码评审 代码评审 也就是很多人熟悉的 Code Review Wikipedia 上定义是这样的 代码评审 是指对计算机源代码系统化地审查 常用软件同行评审的方式进行 其目的是在找出及修正在软件开发初
  • c#处理json数据

    这篇文章讲的很详细 亲测可行 此外我在添加一点注意事项 1 json转C 实体类 之前用了一个转的不行 害的我半天弄不出来 后面找到一个 JSON转C 实体类 BeJSON com 这个转出来的很不错 一下子就成功了 2 如果想在没有环境的
  • Chrome插件消息传递实例

    首先吐槽 360极速浏览器应用开发平台 的开发文档 在消息传递 http open chrome 360 cn extension dev messaging html 一节中 翻译极其低劣 Sending a request from t
  • SpringBoot通过自定义字段注解以及反射获取对象

    在Java的开发过程中 注解的应用场景是非常广泛的 Java也提供了很多内置的注解 比如 Override Deprecated SuppressWarnings等等 之前也写过一篇注解相关的文章 SpringBoot自定义注解 AOP以及