Mybatis中的反射
- 一、引入MetaObject
- 二、MetaObject 源码分析
- 1. 使用MetaObject
- 三、BeanWrapper源码分析
- 1. MetaClass
- 2. ReflectorFactory
- 3. Reflector
- 四、总结
一、引入MetaObject
在使用Mybatis,编写DQL语句时,查询结果可能会是多个,多变量指定肯定是不现实的。而Mybatis可以进行映射,将JDBC返回的结果映射到实例类或者Map对象中,方便开发者直接使用返回对象,就可以得到从数据库取出来的结果。
映射原理大伙都知道是利用了反射(因为咱就只是通过resulttype或者resultmap给了返回值类型的全限定类名而已啊,底层不用反射,返回的对象哪来的啊🤞)。在Mybatis中,提供了一个很强大的工具类===》MetaObject,翻译为元对象,它可以使用pojo类中本有的getter,setter方法,即使是嵌套的对象也可以。当然也可以获取一些getter用到的属性名啊,setter用到的属性名,类型啊等等。
当然当我们使用Mybatis框架时,如果需要也可以去用它。
注意:上面说的工具类是因为我觉得它的构造函数是私有的,而且被开发者拿来使用,但是它可以通过其内部的静态方法forObject
去得到其对象的。
二、MetaObject 源码分析
在具体分析之前,看看MetaObject中的构造函数和其内部属性(都是final类型的),MetaObject只提供了一个有参构造(私有的),如下:
private final Object originalObject;// 实体对象
private final ObjectWrapper objectWrapper;// 对象的封装,内部是一些属性名什么getset方法的封装
private final ObjectFactory objectFactory;// 对象工厂
private final ObjectWrapperFactory objectWrapperFactory;// 对象封装工厂,可拓展
private final ReflectorFactory reflectorFactory;// 用来构造Reflect对象的反射工厂
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory,
ReflectorFactory reflectorFactory) {
// 属性赋值
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
if (object instanceof ObjectWrapper) {
// 判断实例对象是否已经实现过了ObjectWrapper
this.objectWrapper = (ObjectWrapper) object;
}
else if (objectWrapperFactory.hasWrapperFor(object)) {
// 判断这个对象是不是mybatis内置的CustomCollection,或者是不是继承了Author实体类(这个最后还是用BeanWrapper处理的,感觉没啥用)
// 当然这些判断是基于传过来的工厂对象的
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
}
else if (object instanceof Map) {
// 如果是map及其实现的话,其使用封装为mapwrapper,但map只能是<String,Object>
this.objectWrapper = new MapWrapper(this, (Map) object);
}
else if (object instanceof Collection) {
// 这封装里面全是抛异常
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
}
else {
// 其他实力类封装
// 封装了MetaClass对象
this.objectWrapper = new BeanWrapper(this, object);
}
}
可以看见是通过if-elseif-else去初始化objectWrapper
对象的。
ObjectFactory继承结构
Mybatis在核心配置中可以对ObjectFactory进行配置,在setting标签的子标签objectFactory进行配置,如果你觉得可以比源码写的好的话,当然也可以自己写一个对象工厂,去继承ObjectFactory,然后配置上去。(感觉意义不打,如果有用的话记得告诉小编😬)
下面是Mybatis官方文档的案例
下面再看看MetaObject提供的getValue和setValue,供外界使用的,用了PropertyTokenizer对象,用了迭代器设计模式
。(简单看看不细说,具体实现比较简单,用了MetaClass中的Reflector对象…后面小编会去解析Reflector这个核心类)
public Object getValue(String name) {
// 这个属性分词器,就解析你字符串用的,解析成属性名
// 得到属性名好执行反射对象中封装好的get方法
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());
}
}
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);
}
}
1. 使用MetaObject
可以看看具体怎么个使用(不出意外,也用不上)。但感觉如果自己写什么小框架方便开发的,小编觉得它是好工具。
@Test
public void testMetaObject(){
/**
* user中的属性
* private Integer id;
* private String username;
* private String password;
* private List<Integer> list;
* private Student stu;
*/
User user = new User();
MetaObject metaObject = MetaObject.forObject(user,
new DefaultObjectFactory(),
new DefaultObjectWrapperFactory(),
new DefaultReflectorFactory());
metaObject.setValue("id",1);
metaObject.setValue("username","林奕");
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
metaObject.setValue("list",list);
// collection集合、map集合、数组都是用[索引]的形式
System.out.println(metaObject.getValue("list[0]"));
Student stu = new Student();
stu.setName("美琪");
metaObject.setValue("stu",stu);
// 如果内部还有其他实例对象,会创建其MetaObject,再次调用getValue
// 可以假设理解为递归调用了getValue,但实际不是同一个对象调用的
System.out.println(metaObject.getValue("stu.name"));
System.out.println(user);
}
三、BeanWrapper源码分析
在初始化objectwrapper对象封装对象的时候,要么是实体类封装,要么是map封装,map封装前面做过了判断,用得更多的还是映射到实体类的属性上,而其所做的封装都得归功于BeanWrapper
。所以这里对BeanWrapper类进行源码分析。
下面是BeanWrapper继承结构
下面是BeanWrapper类中的属性和构造函数(其他方法都是对ObjectWrapper方法的实现)
private final Object object;// 传过来的实例对象
private final MetaClass metaClass;// 元字节码对象(内部封装了reflector对象,用于获取所需getter方法,setter方法,或属性等等)
public BeanWrapper(MetaObject metaObject, Object object) {
super(metaObject);
this.object = object;
// 通过MetaClass中的forClass获取MetaClass对象,和MetaObject对象获取方式一样
// MetaClass构造方法私有
this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());
}
下面看看metaClass对象是干啥的,可以看见获取MetaClass对象需要俩参数:
- 通过实例对象获取的
Class对象
; - 通过metaObject获取的
ReflectorFactory反射工厂对象
。
1. MetaClass
下面是其属性和构造方法及forClass方法
// 方法很简单,就是去new MetaClass对象
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
return new MetaClass(type, reflectorFactory);
}
private final ReflectorFactory reflectorFactory;// 反射工厂,用于得到reflector对象的。
private final Reflector reflector;// 反射对象
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
// 通过反射工厂去获取reflector对象
this.reflector = reflectorFactory.findForClass(type);
}
2. ReflectorFactory
reflectorFactory反射工厂还提供缓存,开始支持高并发的,然后去除了。
可以看看其默认实现的反射工厂代码,它是利用ConcurrentHashMap
对象,保障了在多线程环境下的安全。当然也提供了缓存的作用,如果对应的type存在就直接给出反射对象,不存在会存入reflectMap,会得到重新new的反射对象。
这个反射工厂也可供外界使用,而且可拓展。
3. Reflector
通过MetaObject setvalue,getvalue方法去操作实体对象是依赖于objectwrapper对象的,objectwrapper利用了多态,调用其中的方法实际是调用BeanWrapper对象中的(拿BeanWrapper举例),而BeanWrapper对象依赖于MetaClass对象,而MetaClass对象依赖于Reflector对象。核心反射实现最后还是到了Reflector类,其余是一层一层的调用。下面对Reflector进行源码分析。
下面是其构造函数及其属性
private static final MethodHandle isRecordMethodHandle = getIsRecordMethodHandle();
private final Class<?> type;
// get方法中用到的属性
private final String[] readablePropertyNames;
// set方法中用到的属性
private final String[] writablePropertyNames;
// 与get类似的操作
private final Map<String, Invoker> setMethods = new HashMap<>();
// 封装get方法,key是get后的名(会将第一个字符转小写),value是方法
private final Map<String, Invoker> getMethods = new HashMap<>();
// 与get一样的操作
private final Map<String, Class<?>> setTypes = new HashMap<>();
// 封装get方法的返回值,key是get后的名(会将第一个字符转小写),value是返回值类型字节码
private final Map<String, Class<?>> getTypes = new HashMap<>();
private Constructor<?> defaultConstructor;
// 这个是把getter、setter 方法中用到的属性名进行大写转换然后封装起来
// key是大写转换后的,value是属性名
private final Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
public Reflector(Class<?> clazz) {
type = clazz;
// 这里是获取无参构造
addDefaultConstructor(clazz);
Method[] classMethods = getClassMethods(clazz);
if (isRecord(type)) {
addRecordGetMethods(classMethods);
} else {
addGetMethods(classMethods);// get方法封装
addSetMethods(classMethods);// set方法的封装
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);
}
}
下面拿get方法封装举例。(再往下就是反射操作了)
private void addGetMethods(Method[] methods) {
// 因为存在getXX,isXX吧,所以用List作为value
Map<String, List<Method>> conflictingGetters = new HashMap<>();
// 这里filter需要有返回值的,需要满足getter条件的,就是以get或is开头的方法
Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
// 获取属性名(get后面的,is后面的,首字符转小写)
// 还得不能冲突,及首字符不让用$,还有属性名不能是class......
.forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
// 解析这个conflictingGetters
// 将满足条件的存入到getMethods,getTypes中
resolveGetterConflicts(conflictingGetters);
}
四、总结
-
可以通过MetaObject对象对实例对象属性赋值和获取;
-
ObjectFactory工厂对象可拓展;
-
PropertyTokenizer类用了迭代器设计模式,对属性名进行操作迭代;
-
BeanWrapper依赖MetaClass,MetaClass依赖反射工厂对象和反射对象;
-
Reflector对象是对实例对象构造器、getter、setter方法、属性名等的封装;
-
编写Bean对象的时候,应该遵循Bean规范,使得get后和set后可以和属性名对应上,从而好进行操作。本质是不管你属性名对不对应的上,反正get后和set后的名得和数据库列名对应上。