今天来看一下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);
}
}
初始化之后改类的所有方法都会有记录,属性也都有记录。有兴趣的朋友可以看下它里面的实现,很有借鉴意义。
metaObject
的getValue
实现其实都是利用迭代器一层层调用,分层是利用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这个类去做,会方便很多。