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

本文介绍了如何使用Java自定义字段注解和反射技术将数据库查询结果转换为对象。定义了`@DbFieldProperty`注解,用于指定字段与数据库列的映射关系。通过反射获取类的注解字段,然后将数据库查询结果集按注解映射转换为对象列表。提供了一个完整的示例,包括从数据库查询数据、创建对象以及测试转换效果。源码已上传至Gitee。

在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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值