Mybatis源码阅读系列(八)

12 篇文章 0 订阅
9 篇文章 0 订阅

今天来看一下Mybatis里面的一个强大的工具类-MetaObject。为啥说它强大呢?因为它可以给任何深度的复杂嵌套类型进行取值和赋值。

MetaObject示例

我们知道在Mybatis里面针对select场景需要对最后的数据封装成对应的resultType或者resultMap这样的javaBean返回。如果你去实现这块功能你会如何去做呢?

我以前写过一个demo类似下面这样:

public static <T> List<T> assembleJavaBean(ResultSet resultSet, Class<T> beanClass) throws Exception {
    // 获取Bean对象内的所有属性
    Field[] fields = beanClass.getDeclaredFields();
    Map<String, List<Field>> fieldMap = Stream.of(fields).collect(Collectors.groupingBy(d -> d.getName()));
    List<T> beanList = new ArrayList<>();
    if (resultSet != null) {
        while (resultSet.next()) {
            // 每当有一行数据就创建一个Bean对象
            T object = beanClass.getDeclaredConstructor().newInstance();
            ResultSetMetaData metaData = resultSet.getMetaData();
            int size = metaData.getColumnCount();
            for (int i = 1; i <= size; i++) {
                String name = metaData.getColumnLabel(i);
                //将数据库表格字段转成驼峰字符串,比如order_name会转成orderName
                String newStr = doConvert(name);
                //利用反射填充值
                if (fieldMap.get(newStr) != null) {
                    Field field = fieldMap.get(newStr).get(0);
                    field.setAccessible(true);
                    System.out.println("field:" + name);
                    field.set(object,resultSet.getObject(name));
                }
            }
            beanList.add(object);
        }
    }
    return beanList;
}

private static String doConvert(String name) {
    if (!name.contains("_")) return name;
    String tempName = name.replace("_", "");
    char[] chars = new char[tempName.length()];
    String[] s = name.split("_");
    int index = 0;
    for (int j = 0; j < s.length; j++) {
        if (j != 0) {
            String t = s[j].substring(0, 1).toUpperCase() + s[j].substring(1);
            System.arraycopy(t.toCharArray(), 0, chars, index, t.length());
            index += t.length();
        } else {
            System.arraycopy(s[j].toCharArray(), 0, chars, index, s[j].length());
            index += s[j].length();
        }
    }
    String newStr = new String(chars);
    chars = null;//help gc
    return newStr;
}

会根据resultSet和传入的class对象产生javaBean。

那MyBatis是怎么做的呢?它引入了ParamMapping的映射,弥补了我们直接写死的那块只能取解决下换线的映射关系。

再来一个就是创建了MetaObject这个类,它不仅仅可以进行赋值,还可以获取类的属性值,而且支持嵌套,比如你在xml语句里面使用a.b.c.d这种,它就能读取a类b属性下的c属性下的d的值。

它的使用其实很简单,有一个静态的工具方法直接使用,SystemMetaObject.forObject(xxx);你只要传入一个对象,即可,然后就可以使用它的getValue和setValue拿值或者赋值。这个setValue其实也是支持多层嵌套的,只是在mybatis里面的数据封装都只有一层,没有用到那么强大的功能而已。

比如有下面这个类:

public class Person {

  private Person person;
  private String name;
  private String pname;

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

  private List pList = new ArrayList(){
    private static final long serialVersionUID = 333L;
    {
      add("2");
    }
  };


  public Person getPerson() {
    return person;
  }

  public void setPerson(Person person) {
    this.person = person;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Map<String, Object> getpMap() {
    return pMap;
  }

  public void setpMap(Map<String, Object> pMap) {
    this.pMap = pMap;
  }

  public List getpList() {
    return pList;
  }

  public void setpList(List pList) {
    this.pList = pList;
  }
}

我们可以进行类似下面的方式赋值和取值。

Person person=new Person();
MetaObject meta = SystemMetaObject.forObject(person);
meta.setValue("name", "li");
meta.setValue("pName", "wang");
meta.setValue("pMap.name", "li");//map里面生成一个key-value是name-li的数据
meta.setValue("pMap[name]", "li");
meta.setValue("pList[1]", "li");
meta.setValue("person.pMap[name]", "li");
meta.getValue("name");
meta.getValue("person.pMap[name]");
meta.getValue("pMap.name");
、、、、、、、、、、、、、省略

在你生成了metaObject对象之后,就可以对其进行赋值和取值,如果是属性可以用.进入,如果是map可以用下标也可以用.,list可以用下标。

MetaObject实现原理

metaObject是依靠什么原理做到这些的呢?底层当然是反射,我们可以看到它里面有五个属性:

public class MetaObject {

  private final Object originalObject;
  private final ObjectWrapper objectWrapper;
  private final ObjectFactory objectFactory;
  private final ObjectWrapperFactory objectWrapperFactory;
  private final ReflectorFactory reflectorFactory;
  、、、、、、、、、、省略
}

第一个属性是原始对象,第二个是包装器,属性的获取和赋值都在这里实现,第三个对象工厂,用于创建对象,第四个是包装器工厂,第五个是反射工厂,在初始化的时候会拿到所有的方法和字段,用于后面的赋值和取值。

里面最核心的是Reflector的构造方法,会将这个类所有需要的数据都构造好,供后面调用。如下:

  public Reflector(Class<?> clazz) {
    type = clazz;
    addDefaultConstructor(clazz);
    Method[] classMethods = getClassMethods(clazz);
    if (isRecord(type)) {
      addRecordGetMethods(classMethods);
    } else {
      addGetMethods(classMethods);
      addSetMethods(classMethods);
      addFields(clazz);
    }
    readablePropertyNames = getMethods.keySet().toArray(new String[0]);
    writablePropertyNames = setMethods.keySet().toArray(new String[0]);
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
  }

初始化之后改类的所有方法都会有记录,属性也都有记录。有兴趣的朋友可以看下它里面的实现,很有借鉴意义。

metaObjectgetValue实现其实都是利用迭代器一层层调用,分层是利用PropertyTokenizer这个类,如果是最后一层,直接返回数据,有的话会先构造下一层的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;
    } else {
      return metaValue.getValue(prop.getChildren());
    }
  }

setValue实现也是类似的,到了最后一层直接赋值,否则否则构建子类metaObject,重复以前过程:

  public void setValue(String name, Object value) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        if (value == null) {
          // don't instantiate child path if value is null
          return;
        }
        metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
      }
      metaValue.setValue(prop.getChildren(), value);
    } else {
      objectWrapper.set(prop, value);
    }
  }

这里注意ObjectWrapper是有多种实现的,不同的类型实现会有差异,比如map的实现就是put,而不是像BeanWrapper一样用反射去赋值。

在这里插入图片描述

总结

今天看了一下metaObject的使用示例和实现逻辑,对于日常开发里面一些嵌套比较深的结构,如果不想写复杂的逻辑去赋值或者取值,可以利用MetaObject这个类去做,会方便很多。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值