Java高级特性-反射:使用反射,把对象转换成 MongoDb 的结构

反射是 Java 的一个高级技巧,大量地用在各种开源项目上。比如,Spring、Tomcat、Jetty 等等项目中,都大量地用到了反射。

作为 Java 程序员,我们如果用好反射,不但能提高自己的技术水平,还能开发出更好的项目。

然而,虽然很多人听说过反射,但却不知道应该用在哪里。

那么,我们就从实际工作出发,使用反射,把对象转换成 MongoDb 的数据结构。当你在搞懂这个例子后,就能明白反射是怎么个用法。

需求分析

在电商系统中,一些数据要保存到 MongoDb 中,以此来提高查询的性能。但在此之前,我们必须把数据先转换成 MongoDb 的结构,也就是把 Java 对象转换成 Document。

比如,订单信息要存到 MongoDb 中,就得把订单对象转换成 Document。

可这样一来,每个实体类都得开发一个 2Doc() 方法。这个方法毫无技术含量,就是把各种字段 put 到 Document 里面。而且一旦字段多了,一不留神就会写错代码,你感受一下。

public class Order {
    private Long id;
    private Long userId;
    private String orderNo;
    private BigDecimal amount;
    private String createTime;
    private String updateTime;
    // 省略无数字段
    
    // 转换方法:订单转doc
    public Document order2Doc(Order order) {
        Document doc = new Document();

        doc.put("id", order.getId());
        doc.put("userId", order.getUserId());
        doc.put("orderNo", order.getOrderNo());
        doc.put("amount", order.getAmount());
        doc.put("createTime", order.getCreateTime());
        doc.put("updateTime", order.getUpdateTime());
        // 省略无数put...

        return doc;
    }
}

除此之外,我们还得从 MongoDb 中取数据,把 Document 转换回 Java 对象,你再感受一下。

public class Order {
    private Long id;
    private Long userId;
    private String orderNo;
    private BigDecimal amount;
    private String createTime;
    private String updateTime;
    // 省略无数字段
    
    // 转换方法:doc转订单
    public Order doc2Order(Document doc) {
        Order order = new Order();
        
        order.setId((Long) doc.get("id"));
        order.setUserId((Long) doc.get("userId"));
        order.setOrderNo((String) doc.get("orderNo"));
        order.setAmount((BigDecimal) doc.get("amount"));
        order.setCreateTime((String) doc.get("createTime"));
        order.setUpdateTime((String) doc.get("updateTime"));
        // 省略无数set...

        return order;
    }
}

光是一个订单类都这么麻烦了,何况这样的类不止一个,而且项目总有新需求,如果一个字段改了,那你麻烦大了,说不定要把整个项目翻一遍。

因此,为了少出错,必须优化这两个转换方法,而这次优化用到了 Java 的两个高级特性:反射、泛型。为了让大家更直观的了解,我将分成两个版本迭代。

第一版,利用反射,简化实体类的转换方法;

第二版,利用泛型、反射,提取 MongoDb 工具类;

接下来,我们就一步步迭代吧~

利用反射,简化实体类的转换方法

在第一版的迭代中,我们要简化实体类的两个转换方法。

我们先从 Java 对象转 Document 开始,还是以 Order 类为例。

首先,我们通过反射,获取到订单类的所有字段信息;然后,使用循环遍历这些字段;最后,在循环中,我们放开字段的访问权限,把字段 put 到 Document 里面。

public class Order {
    // ...省略无数字段

    public Document order2Doc(Order order) throws Exception {
        Document doc = new Document();

        // 获取所有字段:通过 getClass() 方法获取 Class 对象,然后获取这个类所有字段
        Field[] fields = order.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 开放字段操作权限
            field.setAccessible(true);
            // 设置值
            doc.put(field.getName(), field.get(order));
        }

        return doc;
    }
}

你可以看到,经过反射改造后,代码简单了很多。一个对象无论有多少个字段,要写多少 put 操作,只要这几行代码就能搞定。Java 对象转成 MongoDb 的结构,看起来也不那么麻烦了。

照着这个思路,我们再来改造第二个方法,Document 转 Java 对象。

public class Order {
    // ...省略无数字段

    public Order doc2Order(Document doc) throws Exception {
        Order order = new Order();

        for (String key : doc.keySet()) {
            // 获取字段
            Field field = order.getClass().getDeclaredField(key);
            // 开放字段操作权限
            field.setAccessible(true);
            // 设置值
            field.set(order, doc.get(key));
        }

        return order;
    }
}

首先,我们使用循环遍历 Document;在循环中,使用反射获取相应的字段,再放开字段的访问权限,把 Document 的值设置到对象的字段里。

到了这儿,我们利用反射,简化了两个实体类的转换方法,第一版的迭代基本完成了。剩下的工作,就是复制粘贴,把各个类重新改造一遍。

然而,经过这一版迭代,虽然减少了很多工作,但依然有很多不合理的地方。

首先,重复代码还是很多。每个实体类都有两个转换方法,但这两个方法的核心逻辑是一样的,完全没必要到处复制。

然后,这不是实体类应该承担的功能。实体类只负责短暂保留数据,不负责任何持久化功能。你把数据存到哪里,该转换成什么数据结构,这和实体类没什么关系。

换句话说,我们还得做第二次迭代。

利用泛型、反射,提取 MongoDb 工具类

简单来说,泛型是一种风格或范式,你不用一开始就指明具体的参数类型,而是在使用的时候再确定参数类型。

如果把泛型、反射结合在一起,能帮我们减少很多重复代码。

我们来看看,该怎么做第二次迭代?

先从 Java 对象转 Document 开始。我们先声明一个泛型方法;然后,通过反射,获取泛型类的所有字段信息,再使用循环遍历这些字段;最后,在循环中,把字段 put 到 Document 里面。

public class MongoDbUtils {

    // 定义泛型方法:
    // 1. 在返回值前,声明泛型参数 <参数名>;
    // 2. 传入参数时,指定一个泛型参数
    public static  Document obj2Doc(T obj) throws Exception {
        Document doc = new Document();

        // 获取所有字段:通过 getClass() 方法获取 Class 对象,然后获取这个类所有字段
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 开放字段操作权限
            field.setAccessible(true);
            // 设置值
            doc.put(field.getName(), field.get(obj));
        }

        return doc;
    }
}

在加入泛型后,重复代码大量减少了,实体类不用再单独写 2Doc()方法了。在使用的时候,只要调用 MongoDbUtils.obj2Doc() 就行。

按照同样的思路,我们继续来改造第二个方法,Document 转 Java 对象。

public class MongoDbUtils {

    // 定义泛型方法:
    // 1. 在返回值前,声明泛型参数 <参数名>;
    // 2. 传入参数必须是 Class,但这个 Class 是泛型参数,不限制类型
    public static  T doc2Obj(Document doc, Class clazz) throws Exception {
        // 实例化泛型对象
        T obj = clazz.newInstance();

        for (String key : doc.keySet()) {
            // 获取字段
            Field field = clazz.getDeclaredField(key);
            // 开放字段操作权限
            field.setAccessible(true);
            // 设置值
            field.set(obj, doc.get(key));
        }

        return obj;
    }
}

首先,我们定义实例化一个泛型对象;然后,我们使用循环遍历 Document;最后,在循环中,使用反射获取相应的字段,把 Document 的值设置到泛型对象的字段里。

第二版的迭代就基本完成了。我们在第一版迭代的基础上,加入了泛型,得到了一个工具类 MongoDbUtils,这个工具类得到结果和以前完全一样,你可以看下测试代码。

public static void main(String[] args) throws Exception {
    Order order = new Order();
    order.setId(0L);
    order.setUserId(0L);
    order.setOrderNo("1");
    order.setAmount(new BigDecimal("0"));
    order.setCreateTime("2");
    order.setUpdateTime("3");
    System.out.println("原始数据:" + order);

    Document document = MongoDbUtils.obj2Doc(order);
    System.out.println("转换doc数据:" + document);

    Order order1 = MongoDbUtils.doc2Obj(document, Order.class);
    System.out.println("转换java数据:" + order1);
}

运行结果:
原始数据:Order(id=0, userId=0, orderNo=1, amount=0, createTime=2, updateTime=3)
转换doc数据:Document{{id=0, userId=0, orderNo=1, amount=0, createTime=2, updateTime=3}}
转换java数据:Order(id=0, userId=0, orderNo=1, amount=0, createTime=2, updateTime=3)

这样一来,我们就不用保留实体类上的转换方法了,剩下的工作就是删代码。

MongoDb 和 Java 对象的互相转换就完成了。我们做了两次迭代,第一次迭代利用了反射,把大量手动 set/get 操作给去掉了;第二次迭代在原来的基础上,加入了泛型的应用,又去掉了一堆重复代码。

写在最后

反射是一种动态操作类的机制,也是 Java 的高级特性。

这篇文章使用了反射来操作类,免去了自己手动写 set/get 代码的麻烦,消灭了一大堆重复代码。

此外,反射要想发挥作用,有时还得配合其它高级特性。比如,这里把反射和泛型结合,提取出了 MongoDb 工具类,又消灭了分散在各个类中的垃圾代码。

文章演示代码:点击跳转

你可能感兴趣的:(Java高级特性-反射:使用反射,把对象转换成 MongoDb 的结构)