myBatis源码解析-反射篇(4)
前沿
前文分析了mybatis的日志包,缓存包,数据源包。源码实在有点难顶,在分析反射包时,花费了较多时间。废话不多说,开始源码之路。
反射包feflection在mybatis路径如下:
源码解析
1 property包-主要对类的属性进行操作的工具包
1.1 PropertyCopier包利用反射类Filed进行属性复制
// 该类作用将sourceBean与destinationBean相同属性名的属性进行值复制
public class PropertyCopier {
// 属性复制
public static void copyBeanProperties(Class<?> type, Object sourceBean, Object destinationBean) {
Class<?> parent = type;
while (parent != null) {
final Field[] fields = parent.getDeclaredFields(); // 获取该类的所有属性
for(Field field : fields) {
try {
field.setAccessible(true); // 设置该属性的访问权限(包括私有属性)
field.set(destinationBean, field.get(sourceBean)); // 此处调用2个方法,filed.get(objectA) 获取objectA中的filed属性值. filed.set(objectB,value) 将value值赋值给ObjectB的filed属性
} catch (Exception e) { // 异常直接忽略掉(对于非公共属性直接忽略)
// Nothing useful to do, will only fail on final fields, which will be ignored.
}
}
parent = parent.getSuperclass(); // 获取父类,循环复制父类属性
}
} }
该类主要功能是将sourceBean与destinationBean相同属性名的属性进行值复制,是一个属性工具类。
1.2 PropertyNamer根据方法名获取属性名称
public class PropertyNamer {
// 获取getxxx,isxxx,setxxx后的xxx属性
public static String methodToProperty(String name) {
if (name.startsWith("is")) {
name = name.substring(2);
} else if (name.startsWith("get") || name.startsWith("set")) {
name = name.substring(3);
} else {
throw new ReflectionException("Error parsing property name '" + name + "'. Didn't start with 'is', 'get' or 'set'.");
}
// 此处我们默认使用驼峰命名,如setName,那获取的属性名应为name而不是Name
if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
}
return name;
}
该类主要作用是从set,is,get方法中获取属性,是一个属性工具类。
1.3 PropertyTokenizer解析属性集合,此处使用迭代器模式
public PropertyTokenizer(String fullname) {
int delim = fullname.indexOf('.');
if (delim > -1) {
name = fullname.substring(0, delim);
children = fullname.substring(delim + 1);
} else {
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);
}
}
此方法比较简单,举个例子。如我们要解析group[0].user[0].name这一串字符,那经过一次迭代获取如下结构
children = user[0].name
indexedName = group[0]
index = 0
name = group
使用迭代器方法hasNext()判断children是否为null,若不为null,则继续解析。此方法比较重要,在后文中对关于复杂属性的解析,都使用了此类,需要重要理解。
2. Invoker包分析 - 主要对反射类Filed,Method方法进行封装
2.1 执行器接口(将设置属性,获取属性,方法执行全都用Invoker进行封装,充分体现了面向接口编程)
public interface Invoker {
// 执行方法
Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;
Class<?> getType();
}
提供对外通用接口,具体执行器需实现此接口。
2.2 获取对象属性的执行器
public class GetFieldInvoker implements Invoker {
public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
return field.get(target); // 调用反射类Filed.get()方法,获取对象属性
}
}
GetFieldInvoker内部封装了Filed.get()方法获取对象属性。
2.3 设置对象属性执行器
public class SetFieldInvoker implements Invoker {
public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
field.set(target, args[0]); // 调用Filed.set()方法,设置对象属性
return null;
}
}
SetFieldInvoker内部封装了Filed.set()方法设置对象属性。
2.4 对象方法执行器
public class MethodInvoker implements Invoker {
private Class<?> type;
private Method method;
public MethodInvoker(Method method) {
this.method = method;
if (method.getParameterTypes().length == 1) {
type = method.getParameterTypes()[0]; // 获得方法参数列表中的第一个参数类型
} else {
type = method.getReturnType(); // 否则获取方法的返回类型
}
}
public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
return method.invoke(target, args); // 执行target的method方法
}
public Class<?> getType() {
return type;
}
}
MethodInvoker内部封装了method.invoke来执行方法。
3 reflection包-此包中的类基本是增强类,提供对外开放的API
3.1 Reflector类-class类的增强类
public class Reflector { // 反射器,class的增强类
private static boolean classCacheEnabled = true;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
// 相当于缓存工厂,此处使用REFLECTOR_MAP目的是个人理解是因为Reflect的API很耗资源,所以用REFLECTOR_MAP将要反射的类及增强类放置在一起,以后使用时可以直接取不需要重复新建class的增强类了
private static final Map<Class<?>, Reflector> REFLECTOR_MAP = new ConcurrentHashMap<Class<?>, Reflector>();
private Class<?> type; // 该类的类信息
private String[] readablePropertyNames = EMPTY_STRING_ARRAY; // 可读属性
private String[] writeablePropertyNames = EMPTY_STRING_ARRAY; // 可写属性
private Map<String, Invoker> setMethods = new HashMap<String, Invoker>(); // set方法
private Map<String, Invoker> getMethods = new HashMap<String, Invoker>(); // get方法
private Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>(); // setxxx中xxx类型
private Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>(); // getxxx中的xxx类型
private Constructor<?> defaultConstructor; // 该类的默认构造函数
private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();
......
}
查看Reflector的基本属性,主要对一个类按照反射包括的数据结构(方法,属性,构造方法)进行解析。如User类中有age,name属性,且有get,set方法,那木在初始化时会将这些属性和属性,方法等解析出来。注意此类有一个静态列表,用于存放已解析好的类。用于当做缓存使用。查看构造方法验证。
private Reflector(Class<?> clazz) { // 构造函数初始化元数据信息
type = clazz;
addDefaultConstructor(clazz); // 添加构造函数
addGetMethods(clazz); // 添加类的get方法
addSetMethods(clazz); // 添加类的set方法
addFields(clazz); // 添加属性
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]); // 获取getxxx中xxx集合
writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]); // 获取setxxx中xxx集合
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writeablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
分析较简单的添加构造函数方法,后面的添加get,set方法较为复杂,限于篇幅,就略过了。
private void addDefaultConstructor(Class<?> clazz) { // 添加反射类元数据的默认构造函数
Constructor<?>[] consts = clazz.getDeclaredConstructors(); // 获取所有构造函数
for (Constructor<?> constructor : consts) {
if (constructor.getParameterTypes().length == 0) { // 找到无参构造函数,即默认构造函数
if (canAccessPrivateMethods()) { // 若是private,则变为可写
try {
constructor.setAccessible(true);
} catch (Exception e) {
// Ignored. This is only a final precaution, nothing we can do.
}
}
if (constructor.isAccessible()) {
this.defaultConstructor = constructor; // 设置默认构造函数
}
}
}
}
3.2 MetaClass类-Reflector类的增强类
该类主要对于复杂的语句进行拆解。如果需分析一个xxx字段在某个类中是否存在set方法,
对于一般的如age单属性,可以直接调用Reflector.hasSetter("age")来判断。但如果对于group.user.age这个多属性,需分析group中是否有user的set方法,如果有,则继续分析在user对象中是否存在age的set方法。该实现主要是基于上文分析的PropertyTokenizer类与Reflector类。下文分析MetaClass中重写的hasSetter方法来验证。
// 判断name是否在对象中存在set方法
public boolean hasSetter(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name); // 对复杂语句进行拆解
if (prop.hasNext()) { // 若是复杂语句
if (reflector.hasSetter(prop.getName())) { // 第一层解析的对象属性有get方法
MetaClass metaProp = metaClassForProperty(prop.getName()); // 获取getxxx中xxx的类型,构建成一个MetaClass对象,方便递归
return metaProp.hasSetter(prop.getChildren()); // 递归操作
} else { // 如有一层没有get方法,就直接返回false
return false;
}
} else {
return reflector.hasSetter(prop.getName()); // 简单语句直接调用reflector的方法
}
}
3.3 MetaObject类-对外提供的类
MetaObject类里面存放了真正的对象。前文所分析的都是些静态对象,没有真正涉及到实例对象。分析MetaObject对外接口,其实都是内部调用了ObjectWrapper的方法。分析ObjectWrapper很简单,此处结合一个简单demo,来理解MetaObject的作用。
class User{
private String name;
private String age;
// ..... 省略getName,setName,getAge,setAge方法
}
@Test
public void shouldGetAndSetField() {
User user = new User();
MetaObject meta = SystemMetaObject.forObject(user); // 利用实例对象构建一个MetaObject对象
meta.setValue("name", "xiabing"); // 调用metaObject.setValue方法,实际调用的是objectWrapper方法
assertEquals("xiabing", meta.getValue("name")); // 比较,获取name的属性
}
MetaObject是对外提供api的类。要了解具体的实现,还需继续分析下文的wrapper包。
4. wrapper包-对象装饰包
以下为个人理解:使用wrapper包来封装对象,对外开放统一的set,get方法。比如我们使用一个User对象,要设置名称则需调用user.setName("haha")方法,设置年龄需调用user.setAge("10")。这样对于mybatis可能不太友好,于是使用了wrapper类。只需wrapper.set("name","haha"),wrapper.set("age","10")这样统一的接口方式就能设置属性了。看起来确实简洁许多。
4.1 ObjectWrapper接口,提供基本对外开放的接口
public interface ObjectWrapper { //对象装饰类
Object get(PropertyTokenizer prop); // 获得属性
void set(PropertyTokenizer prop, Object value); // 设置属性
String findProperty(String name, boolean useCamelCaseMapping); //查找属性
String[] getGetterNames(); // 获取getXXX中xxx的集合
String[] getSetterNames(); // 获取setXXX中的xxx的集合
Class<?> getSetterType(String name); // 根据xxx获取setxxx中xxx的类型
Class<?> getGetterType(String name); // 根据xxx获取getxxx中xxx的类型
boolean hasSetter(String name); // 查找是否存在setxxx方法
boolean hasGetter(String name); // 查找是否存在getxxx方法
MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory); // 实例化属性的值
boolean isCollection(); // 该对象是否是集合
public void add(Object element);
public <E> void addAll(List<E> element);
}
4.2 BaseWrapper抽象类-提供集合属性的方法
此类教简单,暂且忽略掉。
4.3 BeanWrapper - 真实执行方法的类
此类是反射的关键,是真正调用反射执行get,set,method方法的类。分析其基本属性,注意继承了BaseWrapper,而BaseWrapper中也有一个属性是MetaObject,当时说了MetaObject真正调用方法的是BaseWrapper类,可见,两个对象是一一对应关系。
public class BeanWrapper extends BaseWrapper { // bean的封装类
private Object object; // 真实对象
private MetaClass metaClass; // 该对象的反射类的增强类
分析get属性方法方法
public Object get(PropertyTokenizer prop) {
if (prop.getIndex() != null) { // 分析PropertyTokenizer可知,若index不为null,则代表是集合对象,限于篇幅,小伙伴可自行分析
Object collection = resolveCollection(prop, object);
return getCollectionValue(prop, collection);
} else {
return getBeanProperty(prop, object); // 调用内部方法
}
}
private Object getBeanProperty(PropertyTokenizer prop, Object object) {
try {
Invoker method = metaClass.getGetInvoker(prop.getName()); // 根据属性拿到getFiledInvoker执行器
try {
return method.invoke(object, NO_ARGUMENTS); // 调用getFiledInvoker中的方法
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} catch (RuntimeException e) {
throw e;
} catch (Throwable t) {
throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ". Cause: " + t.toString(), t);
}
}
上面用到了Invoke的方法,Invoke分析见上文。可知面向接口编程的优越性,将getFiled,setFiled,method全都封装成了invoke类。
分析set属性方法
public void set(PropertyTokenizer prop, Object value) {
if (prop.getIndex() != null) { // 分析PropertyTokenizer可知,若index不为null,则代表是集合对象,限
Object collection = resolveCollection(prop, object);
setCollectionValue(prop, collection, value);
} else {
setBeanProperty(prop, object, value); //调用内部方法
}
}
private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
try {
Invoker method = metaClass.getSetInvoker(prop.getName()); // 拿到setFiledInvoker执行器
Object[] params = {value}; // 获取参数
try {
method.invoke(object, params); // 执行setFiledInvoker方法,实际调用Filed.set()方法设置属性
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} catch (Throwable t) {
throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
}
}
总结
由于以前对反射基础不够扎实,导致在分析反射包的时候,过程很不顺利,不过还好坚持下来了。在过程中,发现mybatis的功能架构清晰明了,给了我以后编程的灵感。如果解释有误的还请欢迎评论。任重而道远,如果觉得不错,还请看官点个小赞了。
myBatis源码解析-反射篇(4)的更多相关文章
- myBatis源码解析-数据源篇(3)
前言:我们使用mybatis时,关于数据源的配置多使用如c3p0,druid等第三方的数据源.其实mybatis内置了数据源的实现,提供了连接数据库,池的功能.在分析了缓存和日志包的源码后,接下来分析 ...
- myBatis源码解析-类型转换篇(5)
前言 开始分析Type包前,说明下使用场景.数据构建语句使用PreparedStatement,需要输入的是jdbc类型,但我们一般写的是java类型.同理,数据库结果集返回的是jdbc类型,而我们需 ...
- myBatis源码解析-日志篇(1)
上半年在进行知识储备,下半年争取写一点好的博客来记录自己源码之路.在学习源码的路上也掌握了一些设计模式,可所谓一举两得.本次打算写Mybatis的源码解读. 准备工作 1. 下载mybatis源码 下 ...
- myBatis源码解析-缓存篇(2)
上一章分析了mybatis的源码的日志模块,像我们经常说的mybatis一级缓存,二级缓存,缓存究竟在底层是怎样实现的.此次开始分析缓存模块 1. 源码位置,mybatis源码包位于org.apach ...
- mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)
目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...
- Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例
在Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化.而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅 ...
- Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?
Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? 如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...
- 【MyBatis源码解析】MyBatis一二级缓存
MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...
- Mybatis源码解析-DynamicSqlSource和RawSqlSource的区别
XMLLanguageDriver是ibatis的默认解析sql节点帮助类,其中的方法其会调用生成DynamicSqlSource和RawSqlSource这两个帮助类,本文将对此作下简单的简析 应用 ...
随机推荐
- CondenseNet:可学习分组卷积,原作对DenseNet的轻量化改造 | CVPR 2018
CondenseNet特点在于可学习分组卷积的提出,结合训练过程进行剪枝,不仅能准确地剪枝,还能继续训练,使网络权重更平滑,是个很不错的工作 来源:晓飞的算法工程笔记 公众号 论文:Neural ...
- Spring中异步注解@Async的使用、原理及使用时可能导致的问题
前言 其实最近都在研究事务相关的内容,之所以写这么一篇文章是因为前面写了一篇关于循环依赖的文章: <面试必杀技,讲一讲Spring中的循环依赖> 然后,很多同学碰到了下面这个问题,添加了S ...
- 04 . Filebeat简介原理及配置文件和一些案例
简介 Beats轻量型数据采集器 Beats 平台集合了多种单一用途数据采集器.它们从成百上千或成千上万台机器和系统向 Logstash 或 Elasticsearch 发送数据. Beats系列 全 ...
- [jvm] -- 引用篇
四种引用及其应用场景 强引用 强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收. 使用场景:啥时候都在使用 软引用 软引用在程序内存不足时,会被回收. 使用场景:创建缓存 ...
- Java中static、final和static final(final static)的区别(转)
大佬的总结(大赞!) final可以修饰:属性,方法,类,局部变量(方法中的变量) final修饰的属性的初始化可以在编译期,也可以在运行期,初始化后不能被改变. final修饰的属性跟具体对象有关, ...
- 题解 洛谷 P4695 【[PA2017]Banany】
考虑用动态点分治来解决像本题这样带修的树上路径问题. 首先对原树进行点分治,建出点分树,在点分树每个节点上用动态开点线段树来维护以该节点为起点,到其点分树子树中每个节点的利润. 查询时只需在点分树上当 ...
- 程序员每日一乐:html动态烟花设计 3D
3D版烟花 效果图:file:///C:/Users/QianXin/Desktop/3D%E7%83%9F%E8%8A%B1.html 经过一天的的工作或者学习是否感到枯燥乏味?现在的你是否想找些乐 ...
- spring-cloud-alibaba-sentinel和feign配合使用,启动报Caused by: java.lang.AbstractMethodError: com.alibaba.cloud.sentinel.feign.SentinelContractHolder.parseAndValidateMetadata(Ljava/lang/Class;)Ljava/util/List
背景 我在学习spring-cloud-alibaba技术栈期间,在学习服务熔断与限流的时候,服务启动发生了以下异常 #这是控制台最上面的 sun.misc.Unsafe.park(Native Me ...
- springboot(12)Redis作为SpringBoot项目数据缓存
简介: 在项目中设计数据访问的时候往往都是采用直接访问数据库,采用数据库连接池来实现,但是如果我们的项目访问量过大或者访问过于频繁,将会对我们的数据库带来很大的压力.为了解决这个问题从而redis数据 ...
- SSM框架练习之Jsp页面使用taglib标签报错500的问题
最近在练手一个SSM的基于AdminLET框架模板的后台管理系统,使用的环境是tomcat9,使用Maven构建并通过添加Web模板框架的项目,在添加完所有的配置文件后启动tomcat运行,出现了一个 ...