• MyBatis3源码深度解析(十)MyBatis常用工具类(三)MetaObject&MetaClass


    3.4 MetaObject

    MetaObject是MyBatis提供的反射工具类,可以方便地获取和设置对象的属性值。

    该工具类在MyBatis源码中出现的概率非常高。

    假设有两个实体类:用户信息User和订单信息Order,一个用户可以有多笔订单,因此User类中通过一个List对象记录用户的订单信息。

    public class User {
    
        private List<Order> orderList;
        private Integer id;
        private String name;
        
        // constructor getter setter ...
    }
    
    public class Order {
    
        private String orderNo;
        private String goodsName;
    
        // constructor getter setter ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    下面通过一个示例研究使用MetaObject工具类获取User对象的属性信息:

    @Test
    public void testMetaObject() {
        List<Order> orderList = new ArrayList<Order>() {
            {
                add(new Order("order01", "紫金红葫芦"));
                add(new Order("order02", "羊脂玉净瓶"));
            }
        };
        User user = new User();
        user.setName("银角大王");
        user.setOrderList(orderList);
    
        MetaObject metaObject = SystemMetaObject.forObject(user);
        // 获取第一笔订单的名称
        System.out.println(metaObject.getValue("orderList[0].goodsName"));
        // 获取第二笔订单的编号
        System.out.println(metaObject.getValue("orderList[1].orderNo"));
        // 设置第二笔订单的编号
        metaObject.setValue("orderList[1].orderNo", "order999");
        // 获取第二笔订单的编号
        System.out.println(metaObject.getValue("orderList[1].orderNo"));
        // 判断User对象中是否有orderNo属性
        System.out.println("是否有orderNo属性及其getter方法:" + metaObject.hasGetter("orderNo"));
        // 判断User对象中是否有name属性
        System.out.println("是否有name属性及其getter方法:" + metaObject.hasGetter("name"));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    控制台打印执行结果:

    紫金红葫芦
    order02
    order999
    是否有orderNo属性及其getter方法:false
    是否有name属性及其getter方法:true
    
    • 1
    • 2
    • 3
    • 4
    • 5

    由示例可知,MetaObject工具类不仅可以获取对象的属性信息,还能修改对象的属性。

    示例代码的第一步,是通过SystemMetaObject类的forObject()方法获取一个MetaObject对象。

    源码1org.apache.ibatis.reflection.SystemMetaObject
    
    public static MetaObject forObject(Object object) {
        return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,
            new DefaultReflectorFactory());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    由 源码1 可知,SystemMetaObject的forObject()方法会转调MetaObject的forObject()方法。

    源码2org.apache.ibatis.reflection.MetaObject
    
    public static MetaObject forObject(Object object, ObjectFactory objectFactory,
            ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
        if (object == null) {
            return SystemMetaObject.NULL_META_OBJECT;
        }
        return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
    }
    
    private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory,
                       ReflectorFactory reflectorFactory) {
        this.originalObject = object;
        this.objectFactory = objectFactory;
        this.objectWrapperFactory = objectWrapperFactory;
        this.reflectorFactory = reflectorFactory;
        // object的类型是User,因此会进入else结构中
        if (object instanceof ObjectWrapper) {
            this.objectWrapper = (ObjectWrapper) object;
        } else if (objectWrapperFactory.hasWrapperFor(object)) {
            this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
        } else if (object instanceof Map) {
            this.objectWrapper = new MapWrapper(this, (Map) object);
        } else if (object instanceof Collection) {
            this.objectWrapper = new CollectionWrapper(this, (Collection) object);
        } else {
            this.objectWrapper = new BeanWrapper(this, object);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    由 源码2 可知,MetaObject的forObject()方法会调用MetaObject的构造方法,创建一个MetaObject实例。在构造方法中,由于参数object的类型是User,因此会进入else结构中,创建一个BeanWrapper。

    下面研究一下metaObject.getValue("orderList[0].goodsName")的执行过程。

    源码3:org.apache.ibatis.reflection.MetaObject
    
    public Object getValue(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (!prop.hasNext()) {
            return objectWrapper.get(prop);
        }
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
            return null;
        }
        return metaValue.getValue(prop.getChildren());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    由 源码3 可知,getValue()方法的首先会构造一个PropertyTokenizer对象。

    源码4org.apache.ibatis.reflection.property.PropertyTokenizer
    
    public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
        private String name;
        private final String indexedName;
        private String index;
        private final String children;
    
        public PropertyTokenizer(String fullname) {
            // 如果属性名包含小数点,则说明是查找多层属性
            int delim = fullname.indexOf('.');
            if (delim > -1) {
                // 多层属性
                // 则将第一层保存在name变量中,剩余的保存到children变量中
                name = fullname.substring(0, delim);
                children = fullname.substring(delim + 1);
            } else {
                // 单层属性,只保存name变量,children变量为空
                name = fullname;
                children = null;
            }
            indexedName = name;
            // 判断第一次属性是否包含 [ 符号,有的话说明该属性是个数组或集合
            delim = name.indexOf('[');
            if (delim > -1) {
                // 获取索引值
                index = name.substring(delim + 1, name.length() - 1);
                // 获取属性名
                name = name.substring(0, delim);
            }
        }
        
        @Override
        public boolean hasNext() {
            // 判断children变量是否为空
            return children != null;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    由 源码4 可知,PropertyTokenizer的构造方法会对属性名进行分析。

    在示例代码中,传入的fullName的值为"orderList[0].goodsName",包含小数点,所以会进入第一个if分支,最终得到name=“orderList[0]”,children=“goodsName”,indexedName=“orderList[0]”。

    又因为name的值包含"[",所以也会进入第二个if分支,最终index=“0”,name=“orderList”。

    回到getValue()方法继续执行,因为hasNext()方法是判断children变量是否为空,显然该方法会返回true,因此!prop.hasNext()为false,程序不会进入if分支,而是直接执行下面的metaObjectForProperty()方法,传入参数indexedName=“orderList[0]”。

    源码5org.apache.ibatis.reflection.MetaObject
    
    public MetaObject metaObjectForProperty(String name) {
        // 第二次执行getValue方法
        Object value = getValue(name);
        return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    由 源码5 可知,metaObjectForProperty()方法会第二次调用getValue()方法,但传入的参数值是"orderList[0]"

    它会再次创建一个PropertyTokenizer对象,只是该对象持有的参数与之前的不同,这次的对象中children变量为null。

    由于children变量为null,则!prop.hasNext()为true,getValue()方法会进入if分支,执行ObjectWrapper对象的get()方法。由前面的分析可知,创建的ObjectWrapper对象的类型是BeanWrapper。

    源码6org.apache.ibatis.reflection.wrapper.BeanWrapper
    
    @Override
    public Object get(PropertyTokenizer prop) {
        // 判断索引值是否为空
        if (prop.getIndex() != null) {
            // 不为空,根据Collection的方式处理
            Object collection = resolveCollection(prop, object);
            return getCollectionValue(prop, collection);
        }
        // 为空,按照Bean的方式处理
        return getBeanProperty(prop, object);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    由 源码6 可知,BeanWrapper的get()方法首先判断索引值是否为空,如果不为空,说明是集合,则根据Collection的方式处理;如果为空,则按照Bean的方式处理。

    显然,示例代码有索引值index=0,会进if分支,根据Collection的方式处理,调用resolveCollection方法:

    源码7org.apache.ibatis.reflection.wrapper.BaseWrapper
    
    protected Object resolveCollection(PropertyTokenizer prop, Object object) {
        // prop.getName() → orderList
        if ("".equals(prop.getName())) {
            return object;
        }
        return metaObject.getValue(prop.getName());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    由 源码7 可知,如果prop.getName()不为空,则会第三次调用MetaObject的getValue()方法。

    在示例代码中,此时prop.getName()的值是"orderList",因此不会进入if分支,而是第三次调用MetaObject的getValue()方法。

    它会第三次创建一个PropertyTokenizer对象,只是该对象持有的参数与之前的都不同,这次的对象中index和children变量均为null。

    因此,跟第二次一样,会转调BeanWrapper的get()方法。只是这一次prop.getIndex()为null,按照Bean的方式处理,调用getBeanProperty()方法。

    源码8org.apache.ibatis.reflection.wrapper.BeanWrapper
    
    private Object getBeanProperty(PropertyTokenizer prop, Object object) {
        try {
            // 利用反射机制获取属性的getter方法
            Invoker method = metaClass.getGetInvoker(prop.getName());
            try {
                // 执行getter方法获取属性值
                return method.invoke(object, NO_ARGUMENTS);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        } // catch ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    由 源码8 可知,getBeanProperty()方法会根据属性名利用反射机制获取该属性的getter方法,再执行getter方法获取属性值

    即 源码7 中resolveCollection()方法的返回值,是一个orderList集合:

    回到 源码7 ,继续执行getCollectionValue()方法:

    源码9org.apache.ibatis.reflection.wrapper.BaseWrapper
    
    protected Object getCollectionValue(PropertyTokenizer prop, Object collection) {
        if (collection instanceof Map) {
            return ((Map) collection).get(prop.getIndex());
        }
        // 获取索引值
        int i = Integer.parseInt(prop.getIndex());
        if (collection instanceof List) {
            // 强转为List集合,并取得对应索引的值
            return ((List) collection).get(i);
        } else if (collection instanceof Object[]) {
            return ((Object[]) collection)[i];
        } // else if ... 其他类型的处理
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    由 源码9 可知,getCollectionValue()方法会判断collection参数的类型,并强转为指定的类型,同时还会根据索引值进行取值。示例代码中,collection参数是一个List集合,因此会强转为一个List集合,同时索引值是"0",因此会返回orderList集合的索引为0的元素。

    因此,第三次调用MetaObject的getValue()方法的返回值就是这个索引为0的元素,同时也是第二次调用getValue()方法的返回值,也就是 源码5 的metaObjectForProperty()方法的第一步的返回值。

    由 源码5 可知,metaObjectForProperty()方法的第二步是调用MetaObject的forObject()方法创建一个MetaObject对象,并将上一步查询得到Order对象作为参数传入。

    程序继续执行回到了 源码3 ,即第一次调用MetaObject的getValue()方法。

    接下来进入该方法的最后一行,直接嵌套调用getValue()方法(第四次),传入的prop.getChildren()参数值为"goodsName"。

    第四次调用getValue()方法,首先创建一个PropertyTokenizer对象,该对象属性如下:

    通过前面分析可知,第四次调用getValue()方法,最终会调用getBeanProperty()方法(源码8),根据属性名利用反射机制获取属性的getter方法,再执行getter方法获取属性值。

    示例代码中,会获取"goodsName"属性的getter方法,并调用getter方法获取属性值。

    至此,整个getValue()方法执行结束,最终得到了"orderList[0].goodsName"属性的值。

    简单总结,getValue()方法会判断属性名是否属于多层属性,是的话拆分开来获取,先通过反射机制获取第一层的属性值,再通过反射机制依次获取更深层的属性值。

    3.5 MetaClass

    MetaClass也是MyBatis提供的反射工具类。

    与MetaObject不同的是,MetaObject用于获取和设置对象的属性值,而MetaClass用于获取类相关的信息。如判断类是否有默认构造方法,判断类的属性是否有对应的Getter/Setter方法。

    下面是一个MetaClass的使用示例:

    @Test
    public void testMetaClass() {
        MetaClass metaClass = MetaClass.forClass(Order.class, new DefaultReflectorFactory());
        // 获取所有拥有getter方法的属性名
        String[] getterNames = metaClass.getGetterNames();
        System.out.println(Arrays.toString(getterNames));
        // 判断是否有默认构造方法
        System.out.println("是否有默认构造方法:" + metaClass.hasDefaultConstructor());
        // 判断某属性是否有对应的getter/setter方法
        System.out.println("orderNo属性是否有对应的getter方法:" + metaClass.hasGetter("orderNo"));
        System.out.println("goodsName属性是否有对应的setter方法:" + metaClass.hasSetter("goodsName"));
        // 获取属性的类型
        System.out.println("goodsName属性的类型:" + metaClass.getGetterType("goodsName"));
        // 获取属性的值
        Invoker invoker = metaClass.getGetInvoker("orderNo");
        try {
            Object orderNo = invoker.invoke(new Order("NO.1986", "《西游记》"), null);
            System.out.println("orderNo属性的值:" + orderNo);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    控制台打印执行结果:

    [orderNo, goodsName]
    是否有默认构造方法:true
    orderNo属性是否有对应的getter方法:true
    goodsName属性是否有对应的setter方法:true
    goodsName属性的类型:class java.lang.String
    orderNo属性的值:NO.1986
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    MetaClass工具类的源码,和MetaObject类似。

    本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析

  • 相关阅读:
    软件测试和调试有什么区别?
    kubernetes集群安装Ingress-nginx
    多微信如何自动发朋友圈?
    第100+1步 ChatGPT文献复现:ARIMAX预测肺结核 vol. 1
    linux内核分析:线程和进程创建,内存管理
    第一章 JAVA语言概述
    MySQL夺命10问,你能坚持到第几问?
    ML.NET在C#项目中的使用
    如何在Kubernetes中使用cert-manager部署SSL
    Spring的前置增强,后置增强,异常抛出增强、自定义增强
  • 原文地址:https://blog.csdn.net/weixin_42739799/article/details/136675210