MyBatis源码分析-基础支持层反射模块Reflector/ReflectorFactory
本文主要介绍MyBatis的反射模块是如何实现的。
MyBatis 反射的核心类Reflector,下面我先说明它的构造函数和成员变量。具体方法下面详解。
org.apache.ibatis.reflection.Reflector
public class Reflector {
private final Class<?> type; //对应的Class 类型
//可读属性的名称集合,可读属性就是存在相应getter 方法的属性,初始值为空数纽
private final String[] readablePropertyNames;
//可写属性的名称集合,可写属性就是存在相应setter 方法的属性,初始值为空数纽
private final String[] writeablePropertyNames;
//记录了属性相应的setter 方法, key 是属性名称, value 是Invoker 对象,它是对setter 方法对应
private final Map<String, Invoker> setMethods = new HashMap<>();
//记录了属性相应的getter 方法, key 是属性名称, value 是Invoker 对象,它是对setter 方法对应
private final Map<String, Invoker> getMethods = new HashMap<>();
//记录了属性相应的setter 方法的参数值类型, ke y 是属性名称, value 是setter 方法的参数类型
private final Map<String, Class<?>> setTypes = new HashMap<>();
//记录了属性相应的getter 方法的返回位类型, key 是属性名称, value 是getter 方法的返回位类型
private final Map<String, Class<?>> getTypes = new HashMap<>();
//记录了默认构造方法
private Constructor<?> defaultConstructor;
//记录了所有属性名称的集合
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
public Reflector(Class<?> clazz) {
type = clazz;
//查找clazz的无参构造方法,通过反射遍历所有构造方法,找到构造参数集合长度为0的。
addDefaultConstructor(clazz);
//处理clazz 中的getter 方法,填充getMethods 集合和getTypes 集合
addGetMethods(clazz);
//处理clazz 中的set ter 方法,填充setMethods 集合和set Types 集合
addSetMethods(clazz);
//处理没有get/set的方法字段
addFields(clazz);
//初始化可读写的名称集合
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
//初始化caseInsensitivePropertyMap ,记录了所有大写格式的属性名称
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writeablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
。。。。。。。。。。。。具体代码先忽略,通过构造函数的调用慢慢渗透。
}
1:addDefaultConstructor() // 查找clazz的无参构造方法,通过反射遍历所有构造方法,找到构造参数集合长度为0的。
主要实现的思想是,通过clazz.getDeclaredConstructors();获取所有构造方法集合,然后循环遍历 判断参数长度为0的,并且构造函数权限可控制的设为默认构造方法。
private void addDefaultConstructor(Class<?> clazz) {
Constructor<?>[] consts = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : consts) {
if (constructor.getParameterTypes().length == 0) {
//判断反射对象的控制权限 为true是可控制
if (canControlMemberAccessible()) {
try {
//设置Accessible为true后,反射可以访问私有变量。
constructor.setAccessible(true);
} catch (Exception e) {
// Ignored. This is only a final precaution, nothing we can do.
}
}
if (constructor.isAccessible()) {
this.defaultConstructor = constructor;
}
}
}
}
2:addGetMethods(clazz)// 处理clazz 中的getter 方法,填充getMethods 集合和getTypes 集合
private void addGetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingGetters = new HashMap<>();
//获取当前类以及父类中定义的所有方法的唯一签名以及相应的Method对象。
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
if (method.getParameterTypes().length > 0) {
continue;
}
String name = method.getName();
//判断如果方法明是以get开头并且方法名长度大于3 或者 方法名是以is开头并且长度大于2
if ((name.startsWith("get") && name.length() > 3)
|| (name.startsWith("is") && name.length() > 2)) {
//将方法名截取,如果是is从第二位截取,如果是get或者set从第三位开始截取
name = PropertyNamer.methodToProperty(name); //addMethodConflict 方法内部很简单只有两行代码:
//1:List<Method> list=conflictingGetters.computeIfAbsent(name,K->new ArrayList<>()); 这句话的意思是,在conflictingGetters 的Map中 如果key中存在name,name什么都不做,将value返回,如果name不存在,则返回一个新的ArrayList.
//2:list.add(method); 将方法对象添加到list对象中。
addMethodConflict(conflictingGetters, name, method);
}
}
resolveGetterConflicts(conflictingGetters);
}
2-1:getClassMethods(cls);//获取当前类以及父类中定义的所有方法的唯一签名以及相应的Method对象。
private Method[] getClassMethods(Class<?> cls) {
Map<String, Method> uniqueMethods = new HashMap<>();
Class<?> currentClass = cls;
while (currentClass != null && currentClass != Object.class) {
//currentClass.getDeclaredMethods(),获取当前类的所有方法
//addUniqueMethods 为每个方法生成唯一签名,并记录到uniqueMethods集合中
addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
// we also need to look for interface methods -
// because the class may be abstract
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
addUniqueMethods(uniqueMethods, anInterface.getMethods());
}
currentClass = currentClass.getSuperclass();
}
Collection<Method> methods = uniqueMethods.values();
return methods.toArray(new Method[methods.size()]);
}
2-1-1: addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods()); 为每个方法生成唯一签名,并记录到uniqueMethods集合中
private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
for (Method currentMethod : methods) {
//判断是不是桥接方法, 桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法
if (!currentMethod.isBridge()) {
//获取签名
// 签名格式为:方法返回参数#方法名:参数名 ps:多个参数用,分割 签名样例:String#getName:User
String signature = getSignature(currentMethod);
// check to see if the method is already known
// if it is known, then an extended class must have
// overridden a method
//如果签名存在,则不做处理,表示子类已经覆盖了该方法。
//如果签名不存在,则将签名作为Key,Method作为value 添加到uniqueMethods中
if (!uniqueMethods.containsKey(signature)) {
if (canControlMemberAccessible()) {
try {
currentMethod.setAccessible(true);
} catch (Exception e) {
// Ignored. This is only a final precaution, nothing we can do.
}
}
uniqueMethods.put(signature, currentMethod);
}
}
}
}
2-2: resolveGetterConflicts(conflictingGetters);;//在2-1中返回的方法可能存在,两个相同的方法名称,因为当子类实现父类方法时且参数不同,此时2-1生成的签名是不同的生成签名的规则是 方法返回值#方法名#参数名,那么就会返回两个相同的方法名。 resolveGetterConflicts方法会对这种覆写的情况进行处理,同时将处理后的getter方法记录到getMethods集合中,将其返回值类型填充到getTypes集合中。 内部实现主要是两个for循环,循环比较方法名称相同的情况下,返回值不同的情况下,拿第二个当最终想要的Method。
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
Method winner = null;
String propName = entry.getKey();
for (Method candidate : entry.getValue()) {
if (winner == null) {
winner = candidate;
continue;
}
Class<?> winnerType = winner.getReturnType();
Class<?> candidateType = candidate.getReturnType();
if (candidateType.equals(winnerType)) {
if (!boolean.class.equals(candidateType)) {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
} else if (candidate.getName().startsWith("is")) {
winner = candidate;
}
} else if (candidateType.isAssignableFrom(winnerType)) {
// OK getter type is descendant
} else if (winnerType.isAssignableFrom(candidateType)) {
winner = candidate;
} else {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
}
}
addGetMethod(propName, winner);
}
}
总结一下addGetMethods(clazz)方法和addSetMethods(clazz)大致相同:
首先创建:
Map<String, List<Method>> conflictingGetters = new HashMap<>();
1:获取子类和父类的所有方法。 获取方法是:先生成唯一签名,唯一签名规则是方法返回值#方法名:方法参数1,方法参数2 。 根据签名作为key,method对象作为value生成Map,通过签名进行过滤,将此Map转换为List返回。
2:循环遍历Map,找到符合条件的方法名,is开头或者get开头的,将方法名截取,截取后的方法名作为key,List<Method>作为value,放入到conflictingGetters中。
3:由于子类存在实现父类方法,且返回值不同的情况,导致用一方法名可能有不同的Method ,第三步 resolveGetterConflicts方法会对这种覆写的情况进行处理,同时将处理后的getter方法记录到getMethods集合中,将其返回值类型填充到getTypes集合中。
Reflector Factory 接口主要实现了对Reflector对象的创建和缓存,有三个方法:该接口定义如下:
public interface ReflectorFactory {
boolean isClassCacheEnabled(); //检测该ReflectorFactory对象是否会缓存Reflector对象
void setClassCacheEnabled(boolean classCacheEnabled);//设置是否缓存Reflector对象
Reflector findForClass(Class<?> type); //创建指定class对应的Reflector对象
}
Reflector Factory的实现是DefaultReflectorFactory,具体实现如下:
public class DefaultReflectorFactory implements ReflectorFactory {
private boolean classCacheEnabled = true;
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
public DefaultReflectorFactory() {
}
@Override
public boolean isClassCacheEnabled() {
return classCacheEnabled;
}
@Override
public void setClassCacheEnabled(boolean classCacheEnabled) {
this.classCacheEnabled = classCacheEnabled;
}
@Override
public Reflector findForClass(Class<?> type) {
//如果开启缓存,Reflector对象从ConcurrentMap<Class<?>, Reflector> 取出。
if (classCacheEnabled) {
// synchronized (type) removed see issue #461
return reflectorMap.computeIfAbsent(type, Reflector::new);
} else {//没开启缓存,重新创建。
return new Reflector(type);
}
}
}
DefaultReflectorFactory 的缓存是通过ConcurrentMap来实现的,如果开启了缓存,那么就从ConcurrentMap取Reflector,如果没有开启,就新建Reflector.
除了使用MyBatis提供的DefaultReflectorFactory实现,我们还可以在mybatis-config.xml中配置自定义的ReflectorFactory 实现类,从而实现功能上的扩展。
MyBatis源码分析-基础支持层反射模块Reflector/ReflectorFactory的更多相关文章
- 精尽 MyBatis 源码分析 - 基础支持层
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - 文章导读
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- MyBatis 源码分析-项目总览
MyBatis 源码分析-项目总览 1.概述 本文主要大致介绍一下MyBatis的项目结构.引用参考资料<MyBatis技术内幕> 此外,https://mybatis.org/mybat ...
- MyBatis源码分析-SQL语句执行的完整流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- MyBatis 源码分析 - 映射文件解析过程
1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...
- 精尽 MyBatis 源码分析 - 整体架构
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽 MyBatis 源码分析 - SqlSession 会话与 SQL 执行入口
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 【MyBatis源码分析】环境准备
前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的 ...
- MyBatis 源码分析 - 配置文件解析过程
* 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...
随机推荐
- 聊聊ReentrantLock的内部实现
大家都用过ReentrantLock,但是大家对内部实现是否足够了解呢,下面我就简单说一下其中的实现原理. ReentrantLock是可重入锁,也就是同一个线程可以多次获取锁,每获取一次就会进行一次 ...
- Linux虚拟地址和物理地址的映射
➤背景 一般情况下,Linux系统中,进程的4GB内存空间被划分成为两个部分------用户空间和内核空间,大小分别为0~3G,3~4G.用户进程通常情况下,只能访问用户空间的虚拟地址,不能访问到内核 ...
- loj#2016. 「SCOI2016」美味
题目链接 loj#2016. 「SCOI2016」美味 题解 对于不带x的怎么做....可持久化trie树 对于带x,和trie树一样贪心 对于答案的二进制位,从高往低位贪心, 二进制可以表示所有的数 ...
- AGC 016C.+/- Rectangle(思路 构造)
题目链接 首先想到同样例1的构造方式.即不得不填负的格子填\(-h*w\),其余填\(1\).直接这样能过二三十个点. 只这样不对.比如1 4 1 3,会输出无解(会填[1 1 -3 1]).怎么改呢 ...
- nodejs区分开发环境和生产环境
参考: https://www.cnblogs.com/suoking/p/5509060.html linux&mac 设置dev,producntion export NODE_ENV=p ...
- 常用的机器学习&数据挖掘知识(点)总结
Basis(基础): MSE(Mean Square Error 均方误差), LMS(LeastMean Square 最小均方), LSM(Least Square Methods 最小二乘法), ...
- Python:内置函数
Python所有的内置函数 Built-in Functions abs() divmod() input() open() staticmethod() all() enumerat ...
- JDBC(2)—Statement
介绍: 获取到数据库连接之后,就可以对数据库进行一些增.删.改操作,但是却不能进行查询操作. 增删改操作是程序到数据库的一个操作过程,但是查询是程序到数据库--数据库返回到程序的一个过程. 步骤: 步 ...
- springboot2.X 集成redis+消息发布订阅
需求场景:分布式项目中,每个子项目有各自的 user 数据库, 在综合管理系统中存放这所有用户信息, 为了保持综合管理系统用户的完整性, 子系统添加用户后将用户信息以json格式保存至redis,然后 ...
- SharePoint 2019 离线安装准备工具
前言 最近需要安装SharePoint 2019,然而,服务器并没有网络,所以需要离线安装. 离线安装的步骤很简单,就是把所以得准备工具下载下来,然后修改好命令,使用命令安装即可. 准备工具下载路径 ...