阅读源码

初读 Spring

2017年9月开始阅读 Spring 源码,便在博客上记录自己的阅读笔记,阅读的过程真的是恨痛苦,一个月的时间两大章节还没读完,效果上也不明显。

后面几个月公司业务繁忙更是停下了读源码的节奏。

MyBatis

而12月入了一本《MyBatis 技术内幕》,介绍 MyBatis 的书,想从 MyBatis 入手,并且带着自己的小问题去研究 MyBatis 的源码,去探究一下我用 JSONObject 为什么就可以替代 JavaBean 。

花了一个月的时间便读完了第一遍书,也大致解答了自己的疑惑。发现研究起 MyBatis 确实是轻松很多,总结一下有以下方面的原因:

  • MyBatis 源码量小很多,层次结构清晰,功能明确,确实比 Spring 要简单很多
  • 作者划分章节层次合适
  • 学会了 Debug 源码
  • 拿起了实体书…比9月看电子书时确实方便很多

一本书走完一遍,感觉清楚了很多,对于 MyBatis 剩下的任务,就是再读一遍,特别是带着问题再读,比如去研究它的缓存、配置、反射。

再读 Spring

1月再回过头继续学习 Spring ,其实最大的转变就是,不再执着于见到一段代码就想一直钻到底弄清楚了,Spring 的层次太深,特别容易钻着钻着就把自己绕得不知道在哪了。因此,还是根据书本介绍,文档注释和函数/变量名称大概了解函数的作用先,待刷完一遍之后再回过头二刷再追求搞明白吧。

转变

博客

关于博客的记录,也要做一做转变了。起初写得东西只是笔记,渐渐加入了自己的理解,但是目前来看质量还是不够,有一个很重要的原因就是自己对一些还没有深刻的认识。

前阵子想独立钻研一波 Spring 的 autowiredByType,就匆匆忙忙开了一文,结果读着读着才发现这坑深不见底,实在不该在第一遍时就去碰…

因此,计划博客向更有营养的方向发展,尽量让博客能记录、传播一些能提升自己和其他读者认识的东西。像上一文自己动手实现解决循环依赖就是一个很好的主题,虽然文章写得不够好,技术含量也不够高,但是至少方向上来说确实能学到新东西。

学习

博客的更新频率将会降低,一方面是要自己先学透,提示博文质量,另一方面也是其实后面一段时间将会将重心放在找工作上。毕竟对于技术面试和以前的考试差不多,精读源码实在是性价比有点低,全面复习准备面试题效果更好。虽然我是不喜欢准备面试题的,但没有办法,下一份工作很关键,只有找到稳定的,能追求技术的团队,才能提供安心的钻研技术的环境。

评论和共享

从网上看到一篇博文 徒手撸框架–实现IoC ,写得很棒,作者抛开了 Spring 源码中复杂的校验,拓展等功能,实现了一个极简的 IoC 框架,让 Spring 源码初学者可以清楚的看到 IOC 的实现流程。

本文就借其框架,略加改造,再次介绍一下 Spring 是如何处理循环依赖的。

了解本项目核心代码需要先参考原作者的博文 徒手撸框架–实现IoC

循环依赖

其实很好理解,A 类依赖 B,B 又依赖 A。

说具体点就是 ,我们要 getBean(“a”), A 在实例化时需要为类型为 B 的成员变量赋值,因此去 getBean(“b”),而 getBean(“b”) 的时候又需要为其类型为A 的成员变量赋值,此时又会回过头去实例化 A ,导致无限循环。

用代码展示就是

1
2
3
4
5
6
7
8
public class A {
@AutoWired
private B b;
}
public class B {
@AutoWired
private A a;
}

代码改造

最主要的代码改造在于 BeanFactoryImpl 内, 添加了成员变量

1
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

用于缓存正在创建中的,提前暴露出来的单例 bean。

在获取 bean 时,会在创建之前先从此 Map 中尝试获取,而这就是解决循环依赖的关键。

以上面的例子来说,就是一开始 getBean(“a”) 时,将未完成的 a 放入缓存,getBean(“b”) 时,需要去获取 a ,会从缓存中获取,而不是再去实例化 a。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public Object getBean(String name) throws Exception {
//查找对象是否已经实例化过
Object bean = beanMap.get(name);
if (bean != null) {
return bean;
}
Object earlyBean = earlySingletonObjects.get(name);
if (earlyBean != null) {
System.out.println("循环依赖,提前返回尚未加载完成的bean:" + name);
return earlyBean;
}
//如果没有实例化,那就需要调用createBean来创建对象
BeanDefinition beanDefinition = beanDefineMap.get(name);
bean = createBean(beanDefinition);
if (bean != null) {
earlySingletonObjects.put(name, bean);
//对象创建成功以后,注入对象需要的参数
populatebean(bean, beanDefinition);
//再吧对象存入Map中方便下次使用。
beanMap.put(name, bean);
//从早期单例Map中移除
earlySingletonObjects.remove(name);
}
//结束返回
return bean;
}

Q & A

Q: 构造器循环依赖为什么无法解决?

A: 从上面代码可以看出,需要在 createBean 之后,才能将其放入缓存,而构造过程是在 createBean 之内的,此时尚未构造好一个基本的 bean ,拿什么放入缓存呢?

心得

上面只贴了 getBean 的代码,仅仅修改了原作者不到 10 行代码,其实在修改原框架,实现我们要的功能时不止这么多,包括调整对 json 的解析,对 bean 的填充等。

感受到 Spring 框架真的是很复杂很全面,这复杂程度靠说是说不清楚的,也不是翻一遍书看看源码就能明白的。而且看源码其实还是似懂非懂,中间的细节迷迷糊糊就可能跳过去了。

在追随 Spring 脚步,复现其代码的时候,才更深刻的理解其中很多操作,很多类的作用。比如说 BeanDefinition, BeanWrapper , PropertyDescriptor 这些类在我想要实现一些功能的时候才能体会到 Spring 创造它们的重要性。

评论和共享

前文

源码解析

入参说明

  • includeNonSingletons:是否包括非单例的 bean,比如 prototype scope
  • allowEagerInit:为了这个检查(找出所有匹配类型的 beanName),是否初始化 lazy-init 单例和由 FactoryBeans 创建的对象。此处我们传入的值为 true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public static String[] beanNamesForTypeIncludingAncestors(
ListableBeanFactory lbf, Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {

Assert.notNull(lbf, "ListableBeanFactory must not be null");
//方法主干还是在这行 getBeanNamesForType
String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
//下面的内容就是从 bf 的 parent 中找,
if (lbf instanceof HierarchicalBeanFactory) {
HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
//此处以 parent 再来调此方法,合并结果。
String[] parentResult = beanNamesForTypeIncludingAncestors(
(ListableBeanFactory) hbf.getParentBeanFactory(), type, includeNonSingletons, allowEagerInit);
List<String> resultList = new ArrayList<String>();
resultList.addAll(Arrays.asList(result));
for (String beanName : parentResult) {
if (!resultList.contains(beanName) && !hbf.containsLocalBean(beanName)) {
resultList.add(beanName);
}
}
result = StringUtils.toStringArray(resultList);
}
}
return result;
}

看来我们还需要继续深入到 getBeanNamesForType中去一探究竟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
//configurationFrozen:判断所有 bean 的 定义元数据是否可以被缓存
//如果不能缓存 或没type 或不允许急切初始化,则直接查 doGetBeanNamesForType
if (!isConfigurationFrozen() || type == null || !allowEagerInit) {
return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);
}
//否则先查缓存,没有缓存的话再查 doGetBeanNamesForType 并塞入缓存
Map<Class<?>, String[]> cache =
(includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType);
String[] resolvedBeanNames = cache.get(type);
if (resolvedBeanNames != null) {
return resolvedBeanNames;
}
resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);
//判断是否缓存安全:依据我们目标class 和当前beanFactory的classLoader是否一致
if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) {
cache.put(type, resolvedBeanNames);
}
return resolvedBeanNames;
}

再深入一层到 doGetBeanNamesForType ,其中逻辑外层是遍历所有的 beanName, 对于不是别名的进行处理,处理过程如下(省略了 try-catch ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//首先获取这个beanName对应的mbd,它的相关定义配置信息
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// Only check bean definition if it is complete.
// 不是抽象的
// 并且 允许急切初始化 或 (此bean不需要急切初始化 且(有beanClass 或 不是lazyInit 或 允许急切的类加载,即使是懒惰的初始化bean))
if (!mbd.isAbstract() && (allowEagerInit ||
((mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading())) &&
!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
// In case of FactoryBean, match object created by FactoryBean.
//判断是否 FactoryBean
boolean isFactoryBean = isFactoryBean(beanName, mbd);
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
//接下来又是一段非常长的逻辑判断,判断是否匹配
boolean matchFound =
(allowEagerInit || !isFactoryBean ||
(dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
(includeNonSingletons ||
(dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
isTypeMatch(beanName, type);
if (!matchFound && isFactoryBean) {
// In case of FactoryBean, try to match FactoryBean instance itself next.
//如果不匹配,还要试试匹配FactoryBean本身,因为说不好要的就是这个FactoryBean呢
beanName = FACTORY_BEAN_PREFIX + beanName;
matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
}
if (matchFound) {
result.add(beanName);
}
}

//检查一遍手动的单例集合
//对于 FactoryBean ,如果匹配到它的getObject()满足,就不会继续去匹配它本身
for (String beanName : this.manualSingletonNames) {
// In case of FactoryBean, match object created by FactoryBean.
if (isFactoryBean(beanName)) {
if ((includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type)) {
result.add(beanName);
// Match found for this bean: do not match FactoryBean itself anymore.
continue;
}
// In case of FactoryBean, try to match FactoryBean itself next.
beanName = FACTORY_BEAN_PREFIX + beanName;
}
// Match raw bean instance (might be raw FactoryBean).
if (isTypeMatch(beanName, type)) {
result.add(beanName);
}
}

}

前一段的超长的逻辑判断看得人头疼,但是先看下半段的遍历,突然就找到了最关键判断类型匹配的函数 isTypeMatch,从名字就看出来,这应该就是判断类型匹配的地方啦。

然而点进去一看,居然是长达 100 行的函数。

isTypeMatch

首先从单例中查找,匹配这个单例 bean 的类型和我们目标的类型,其中对 factoryBean 的处理也比较简单,就不再贴代码了。对于注册的 null instance ,也返回 false。

再从父工厂找,递归 isTypeMatch 。

再就是复杂的查找了。

先定义个 typesToMatch ,包括了目标类型和 FactoryBean .

然后我们再继续看代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Check decorated bean definition, if any: We assume it'll be easier
// to determine the decorated bean's type than the proxy's type.
// 先检查 bean 的装饰definition。
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) {
RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);
Class<?> targetClass = predictBeanType(dbd.getBeanName(), tbd, typesToMatch);
if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) {
return typeToMatch.isAssignableFrom(targetClass);
}
}
// 注意此处predictBeanType返回了很关键的Class ,我们后面再详细分析此方法。
Class<?> beanType = predictBeanType(beanName, mbd, typesToMatch);
if (beanType == null) {
return false;
}

// Check bean class whether we're dealing with a FactoryBean.
//接下来就是处理 FactoryBean
if (FactoryBean.class.isAssignableFrom(beanType)) {
if (!BeanFactoryUtils.isFactoryDereference(name)) {
// If it's a FactoryBean, we want to look at what it creates, not the factory class.
beanType = getTypeForFactoryBean(beanName, mbd);
if (beanType == null) {
return false;
}
}
}
else if (BeanFactoryUtils.isFactoryDereference(name)) {
// Special case: A SmartInstantiationAwareBeanPostProcessor returned a non-FactoryBean
// type but we nevertheless are being asked to dereference a FactoryBean...
// Let's check the original bean class and proceed with it if it is a FactoryBean.
beanType = predictBeanType(beanName, mbd, FactoryBean.class);
if (beanType == null || !FactoryBean.class.isAssignableFrom(beanType)) {
return false;
}
}
//对其 resolvableType 进行处理
ResolvableType resolvableType = mbd.targetType;
if (resolvableType == null) {
resolvableType = mbd.factoryMethodReturnType;
}
if (resolvableType != null && resolvableType.resolve() == beanType) {
return typeToMatch.isAssignableFrom(resolvableType);
}
//如果以上都没有处理掉的话, 则判读typeToMatch和 beanType
return typeToMatch.isAssignableFrom(beanType);

总结:

目前很遗憾 isTypeMatch 往下还有很复杂的逻辑暂时不能看懂,但是从外层的逻辑大致知道寻找所有匹配的beanName 的方法非常的“复杂粗暴”。遍历 beanDefinitionNames 已定义的所有 bean,即使是一个小型的项目也有近200个 bean 需要遍历,并且这数百个 beanName 还要遍历非常多次。

只能说根据 type 寻找 bean 实在是比根据 name 复杂了太多太多,从源码看真是深坑,理解了为什么作者直接忽略了这部分…应该是第二遍或第三遍阅读 Spring 源码时才能理解。

评论和共享

前言

Spring 装配 bean 有两种类型:autowireByNameautowireByType

autowireByName 通过名称查找很直接,就是我们一直在学的 getBean() 。

autowireByType 根据类型查找相比起来就要复杂一些了,《 Spring 源码深度解析 》 中有介绍过的部分我就不再重复贴了,但是书中遗漏了一处重要的部分—— findAutowireCandidates 查找所有合适的 bean,还有一处新版本 Spring 中升级的部分——如果只需要一个但是找出多个 bean 该怎么处理,今天我们先来学习第一部分。

正文

本函数要做什么

1
2
3
4
5
6
@RestController
@RequestMapping("/article")
public class ArticleController {
@Autowired
private ArticleService articleService;
}

在初始化 ArticleController 的过程中,我们要为其装配 ArticleService 。

忽略掉外面代码一层一层的包裹之后,我们走到 DefaultListableBeanFactory.findAutowireCandidates 这个函数中,要寻找合适的候选 bean 。由于可能会找到多个,因此返回结果是候选 bean 的名称和其对应实例构成的 Map 。

源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//三个参数的含义依次是 
// 正在解析的 beanName, 本例中即 "articleController"
// 需要装配的 bean 类型, 本例中即 ArticleService.class
// 对当前依赖关系的解析类,记录了 ArticleController 和 ArticleService 的依赖关系
protected Map<String, Object> findAutowireCandidates(String beanName,
Class<?> requiredType,
DependencyDescriptor descriptor) {

//第一步就是查找出所有符合类型的 beanName 。
//似乎第一句就干完全部逻辑了???稍后我们再详细分析这个方法。
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this, requiredType, true, descriptor.isEager());
Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length);
/**
* resolvableDependencies 记录了 依赖类型--具体装配值 的映射
* 遍历 resolvableDependencies。如果该类型是我们需要的类型(ArticleService),
*
*/
for (Class<?> autowiringType : this.resolvableDependencies.keySet()) {
if (autowiringType.isAssignableFrom(requiredType)) {
Object autowiringValue = this.resolvableDependencies.get(autowiringType);
//key值是我们需要的类型,但value值未必。
//value可能是ObjectFactory,就得调用它的 getObject() 来获取真正的bean.
autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
if (requiredType.isInstance(autowiringValue)) {
//如果类型匹配,则塞入result
result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
break;
}
}
}
for (String candidate : candidateNames) {
//如果不是自己依赖自己 , 并且符合装配候选,就塞入result。
//何为符合装配候选(isAutowireCandidate)呢?稍后我们再详细分析。
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) {
// Consider fallback matches if the first pass failed to find anything...
//如果之前一轮都没找到,则考虑回退匹配,什么是回退匹配?稍后再分析。
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
for (String candidate : candidateNames) {
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor)) {
//再执行一遍上面的方法。如果不是自己依赖自己,并且符合装配候选,就塞入result。
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
if (result.isEmpty()) {
// Consider self references as a final pass...
// but in the case of a dependency collection, not the very same bean itself.
// 如果依然没找到结果,那么满足以下条件的也是我们的目标。
// 1.是自引用
// 2.依赖不是多元素依赖 或者 bean名和候选者名字不相等(这里就避免了自引用导致无限循环)
// 3.候选者符合回退匹配之后的装配候选
for (String candidate : candidateNames) {
if (isSelfReference(beanName, candidate) &&
(!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
isAutowireCandidate(candidate, fallbackDescriptor)) {
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
}
}
return result;
}

看完主干自然还是有点迷糊,我们留下了三个问题要继续研究:

评论和共享

Spring vs MyBatis

Spring 和 MyBatis 中都有 BeanWrapper , Spring 中为接口, 实现类为 BeanWrapperImpl , 为了方便后面区分,本文用 SB 指代 Spring 的 BeanWrapperImpl ,用 MB 指代 MyBatis 中的 BeanWrapper。

功能

BeanWrapper 都属于各自框架的反射工具箱的重要组成部分。都是创建实例并且为其属性赋值的。以 SB 为例,下面的代码应该很容易看明白它的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BeanWrapper company = BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");

// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:
BeanWrapper jim = BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");

共同点

  1. 核心功能功能基本相同
  2. 对要生成的类的要求规范也相同,有 get set 方法。
  3. 它们的底层也没有什么秘密,归根到底都是使用 java.lang.reflect 包下的 Constructor , Method 等等工具。

不同点

可能是我刚开始研究 SB 的原因,感觉 SB 源码更加复杂,结构也没那么清楚,关联了向下层级的工具类,很长很长一段的源码。 这篇博文 分析了其中一部分。

得益于已经学习过结构更为清晰的 MB,尽管 SB 源码复杂,但是读的时候不再会恐惧,因为简略的Debug 一遍就知道它底层还是调 getter/setter 的反射。再复杂的结构也离不开这最终的方法。

已目前对 SB 粗浅的了解来说,感受到最大的区别就是在工具箱中的结构地位不同

MB 属于BaseWrapper 的子类之一,同级别的还有 MapWrapper ,上级还有 CollectionWrapper 。

而 SB 就已经是在创建 Bean 时直接使用到的接口了。

解析嵌套参数名 (比如 user.name / address.city.mailcode),类型转换这些事情,SB 都能处理完。而 MB 都是先要使用其它工具类处理,比如依靠 PropertyTokenizer 。

总而言之,SB 就是对外的一个大接口,包含很多功能,MB 则是MyBatis 反射工具箱内的一个小的工具实现。

评论和共享

此方法交给了 AbstractBeanFactory 的子类 AbstractAutowireCapableBeanFactory 去实现。

并且不管这个bean是单例还是 prototype 还是其它 scope ,最终都是会走到此处,只是前后的一些验证、处理有区别。比如单例的就要先去缓存中获取,prototype 就不需要。

源码阅读到这里,我们已经习惯了一层一层剥。createBean 依然还是没有直接地把 bean 创建出来(当然我所期望的看到创建 bean 就是看到它的反射源码为止)。

createBean 的大致步骤为:

  1. 根据 RootBeanDefinition 来获取要创建 bean 的 class 。这 class 还有可能为 null。
  2. prepareMethodOverrides 。准备 override 方法,对 override 属性进行验证。
  3. 给后处理器一个机会来返回代理,替代真正的 bean.
  4. doCreateBean 创建真正的 bean 实例。

prepareMethodOverrides

首先去温习一遍 lookup-method 和 replace-method 吧。博文

其实就是通过配置把原本 bean 中的某个方法给替代掉。

此处我们先只是确认一遍指定的替代方法存在于要生成的 bean 中。

顺带看一看这个方法有没有重载overload),做个标记。

resolveBeforeInstantiation

经过一波预处理器InstantiationAwareBeanPostProcessor ,如果生产出了 bean,再经过一波后处理器。

一旦生产出 bean,则立即将此 bean 返回。

此处就是留下了一个拓展点,经过此方法之后,bean可能已经不是我们认为的 bean 了,而可能已经变成了一个经过处理的代理 bean 。

循环依赖

构造器循环依赖

如果是 prototype,无法解决,只能抛错。

代码在 AbstractBeanFactory : 256 doGetBean()

当创建 bean 时,首先去“当前创建 bean 池”查找是否当前 bean 正在创建,如果发现存在,则表示循环依赖了。抛出 BeanCurrentlyInCreationExcetion 。

当前创建 bean 池:

1
2
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<Object>("Prototype beans currently in creation");

setter 循环依赖

只能解决单例的情况。

在创建单例 bean 时,提前暴露刚完成构造器但未完成其他步骤(如 setter 注入)的 bean 。

通过提前暴露这个单例工厂方法,从而使其他 bean 能够引用到此 bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
//注意这里返回的是 早期引用
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
//单例工厂注册
this.singletonFactories.put(beanName, singletonFactory);
//早期单例移出
this.earlySingletonObjects.remove(beanName);
//注册单例加入
this.registeredSingletons.add(beanName);
}
}
}

简单地记一下这里解决步骤:

testA 先创建,并且暴露一个工厂出去,进行 setter 注入 testB.

testB 创建,并且暴露一个工厂,进行 setter 注入 testA.

在这里想用 testA 时,由于发现提前暴露的工厂,从而在此处走了另一条路,使用此工厂来创建 testA ,在此处解决了循环问题。

再返回回去继续完成 testA 的 setter 注入。

评论和共享

问题

上一篇我们讲到,对于未知数据类型的解析,UnknownTypeHandler 把部分任务交给了 TypeHandlerRegistry ,甚至可能仅仅只丢了一个 javaType 过来就要求返回一个合适的解析器回去 。TypeHandlerRegistry 到底里面做了啥可以找到合适的解析器呢?

功能

从类的名字我们就知道这个类的功能大概就是个注册表,而且很可能是全局共用的,记录各种 javaType,jdbcType,TypeHandler 的映射关系。实际这个类的核心也就是维护了几个 map 。

核心字段

JDBC_TYPE_HANDLER_MAP

1
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

这个很直观,就是注册 jdbcType 和解析器的对应关系。

TYPE_HANDLER_MAP

1
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

书上原文是:“记录了 java 类型向指定的 JdbcType 转换时,需要使用的 TypeHandler 对象。例如:Java 类型中的 String 转换成数据库的 char 、varchar 等多种类型,所以存在一对多关系”。

Type 是 java.lang.reflect 包下的接口,Class 类实现了此接口。所以此 map 的 key 值是 javaType ,比如 String.class 。

UNKNOWN_TYPE_HANDLER

就是上一篇文章学习过的 UnknownTypeHandler 的实例,主要用在 Object.class 和 JdbcType.OTHER 上。

ALL_TYPE_HANDLERS_MAP

1
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

key 是解析器的 class ,value 是解析器自身。

记录了全部的解析器的类型及该类型相对应的 TypeHandler 对象。

NULL_TYPE_HANDLER_MAP

仅仅是一个空 TypeHandler 集合的标识。因为 TYPE_HANDLER_MAP 是ConcurrentHashMap, 不能塞 null 值,因此在需要的地方以此空标识作为 value 替代null塞入。

注册

在构造方法中,就调用了大量的 register(…) 的方法,注册了很多映射关系。

register 有很多重载方法,除了简单的向 JDBC_TYPE_HANDLER_MAP 注册之外,全都最终指向了下面的重载方法。

1
2
3
4
5
6
7
8
9
10
11
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null) {
map = new HashMap<JdbcType, TypeHandler<?>>();
TYPE_HANDLER_MAP.put(javaType, map);
}
map.put(jdbcType, handler);
}
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}

方法并不复杂,我们只需要理解的是这里为两个 map 中都塞入了值,一个是 key 为 javaType 的 map , 也就是 String 与 char/varchar 的一对多的关系。另一个则是记录全部 handler 的map。

这里有一点需要特别留意的就是,比如 String 类,除了映射 char/varchar 之外,还映射 null ,对应的解析器也是 StringTypeHandler 。

1
2
3
4
//这一行就是javaType为String.class jdbcType 为null 的解析器注册
register(String.class, new StringTypeHandler());
//这是普通的注册
register(String.class, JdbcType.CHAR, new StringTypeHandler());

查找 TypeHandler

终于到了查找 TypeHandler 的部分了,看了这么久,是不是差点晕得都忘了我们的这个注册表最核心的功能在这呢?

根据 jdbcType 和 typeHandler 的class 查找解析器的功能都很简单,就是上面的 JDBC_TYPE_HANDLER_MAP 和 ALL_TYPE_HANDLERS_MAP 中取值。

关键的在于根据 javaType 寻找解析器。而且我们的在上一篇也讲到了 PreparedStatement 在赋值的时候,我们没有提供 jdbcType ( null ), 仅仅只有 javaType 。

getTypeHandler 其实就是从 TYPE_HANDLER_MAP 取值.我们入参类型为 String 为例来看看其查找解析器的过程 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
//首先根据 String.class 查找其一对多的解析器集合.
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {
//然后根据其指定的 jdbcType 来找对应的解析器。而此处我们的 jdbcType 是null
//但是在注册的时候我们留意过,对于 null ,同样也进行了注册
//就相当于是给了个默认的解析器
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {
handler = jdbcHandlerMap.get(null);
}
if (handler == null) {
handler = pickSoleHandler(jdbcHandlerMap);
}
}
return (TypeHandler<T>) handler;
}

总结

目前我们已经接触过了两种方式对明确指明的 javaType 的转换。主要可以分为两大类:

ObjectTypeHandler

这个主要依靠 JDBC 底层的方法来查找合适的 javaType ,大量的 switch-case 语句。主要用在将返回值的封装到 JSONObject 中。

1
2
3
4
5
6
7
8
9
10
switch (field.getSQLType()) {
case Types.TINYINT:
if (!field.isUnsigned()) {
return Integer.valueOf(getByte(columnIndex));
}
return Integer.valueOf(getInt(columnIndex));
case Types.SMALLINT:
return Integer.valueOf(getInt(columnIndex));
//...
}

TypeHandlerRegistry

注册表功能,将常规的数十种关系映射在初始化时就都注册好,也就是提前存入 map 中,需要的时候去 map 中取。

尤其是将 javaType 和 TypeHandler 关联起来,并对一些类型注册了默认的解析器,即 jdbcType 未指明时所要采用的解析器。

这个更多是用在 PreparedStatement 入参的赋值时。

评论和共享

问题

前面说到,我们可以用 JSONObject 替代习惯使用的 JavaBean ,而之所以能用 JSONObject 主要就是因为它实现了 Map<String,Object>

实际使用我们就会发现,MyBatis 使用JSONObject 封装返回结果的时候很“智能”,数据库里字段是 varchar 类型,JSONObject 中返回值就是 String 类型,数据库字段是 int/float 类型,JSONObject 中返回值就是对应的数值类型。甚至通过 debug 发现数据库中保存了datetime 类型的数据,JSONObject 中保存的是 java.sql.timestamp 类型,而timestamp 类型继承了常见的 java.util.Date

为什么 MyBatis 可以用得这么爽呢?我们实现可完全没声明需要此字段的 javaType 呢。而且用得爽了,类型转换会不会导致程序性能大打折扣呢?

今天我们就从深入源码,探究一番 MyBatis 到底是怎样做到对未明确声明的字段处理返回类型的。

TypeHandler

MyBatis 类型转换的核心就是这个接口,定义的方法可以看做就两种 setParameter 和 getResult ,很好理解,我们传参和接收 sql 结果时就调用这个。

1
2
3
4
5
6
7
8
9
10
11
public interface TypeHandler<T> {

void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

T getResult(ResultSet rs, String columnName) throws SQLException;

T getResult(ResultSet rs, int columnIndex) throws SQLException;

T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

抽象类 BaseTypeHandler 部分实现了 TypeHandler ,主要完成了对空值的处理。 非空值的处理全部交给了子类完成。

BaseTypeHandler 子类非常多,对应了数据库的各种数据类型,实现都很简单,比如SqlTimestampTypeHandler 处理 Timestamp 类型。

1
2
3
4
5
6
7
8
9
10
11
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Timestamp parameter, JdbcType jdbcType)
throws SQLException {
ps.setTimestamp(i, parameter);
}

@Override
public Timestamp getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getTimestamp(columnName);
}

都是直接调用了 PreparedStatement 和 ResultSet 处理相应类型字段的方法。

很明显,一旦指明了我们需要 MyBatis 给我们返回的此字段类型,MyBatis 肯定就去找到对应的 TypeHandler 实现类去处理。而我们没有指定返回类型的是怎么处理的呢?或者说,对于 Object 类型是怎么处理的呢?

ObjectTypeHandler

我们先来看看这个类,看名字就会猜可能估计未知类型全靠它了吧。提前预告下,并不是哦,getNullableResult 还算经常使用,入参赋值就没见用了,毕竟入参的 JavaType 我们通过反射还是可以找到的。

1
2
3
4
5
6
7
8
9
10
11
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
ps.setObject(i, parameter);
}

@Override
public Object getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getObject(columnName);
}

一看这实现,居然还是调用的 JDBC 底层的对应方法。事实上 com.mysql.jdbc 在处理Object 类型是也是通过大量的 if-else 或 switch-case 来找到本数据真正的类型的。入参绑定依据 parameterObj instanceof 各种类型,返回结果类型依据 field.getSQLType 的各种类型。

UnknownTypeHandler

事实上,MyBatis 在很多我们没有指明参数类型的情况下,都是使用 UnknownTypeHandler 来解决类型转换的。UnknownTypeHandler 中的核心resolveTypeHandler 方法,就是查找对应数据的类型解析器(TypeHandler) , 再用这个合适的 typeHandler 进行解析。

resolveTypeHandler 方法的重载有3种,主要的两种就是一种处理入参的,一种处理返回结果的。

入参类型解析

1
2
3
4
5
6
7
8
9
10
11
12
13
private TypeHandler<? extends Object> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<? extends Object> handler;
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
} else {
handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}

在入参为 null 或者实在找不到解析器的情况下,就会返回我们上面讲的 ObjectTypeHandler 。

而这里面关键的方法就是

1
typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);

typeHandlerRegistry 可以理解为全局共用的各种类型与解析器关系的注册表,后面的文章还会继续深入讲解,我们首先注意这个方法的入参,第一个参数拿到了参数的 class , 第二个参数拿到了 jdbcType 。这不就相当于javaType 和 jdbcType 都有了吗?那即使还没研究 typeHandlerRegistry 到底干了啥,但是条件给的这么充分了,注册表的任务也太轻松了吧!

等等,我们的 DAO 层给入参是 JSONObject 类型,里面 username 字段是 String 类型,money 字段是 float 类型,这些都能通过 getClass() 获取到确实没毛病。但是 jdbcType 是哪来的呢?我们现在可是在处理 PreparedStatement 呢!

通过 debug 我们发现,这里的 jdbcType 我们没有指明的情况下,确实都是 null 。说明 typeHandlerRegistry 里仅仅是通过 javaType 来寻找解析器的。所以 typeHandlerRegistry 还是有很多门道等着我们去探索哦。

返回结果类型解析

对 ResultSet 的解析有两种方式,首先查看此结果字段–比如nickname–在 field 中的序号,如果没序号,则直接返回 ObjectTypeHandler 。如果有序号,则进入下面的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) throws SQLException {
TypeHandler<?> handler = null;
//通过下面两个方法获取到jdbcType和javaType
JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex);
Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex);
//后面的任务就还是交给了typeHandlerRegistry
if (javaType != null && jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
} else if (javaType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType);
} else if (jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(jdbcType);
}
return handler;
}

其中的关键方法还是从 rsmd 中获取 jdbcType 和 javaType ,然后再通过 typeHandlerRegistry 去查找对应的 handler 。

debug 发现几乎每次从 rsmd 中获取 jdbcType 和 javaType 都获取到了,看来玄机都在safeGetJdbcTypeForColumnsafeGetClassForColumn 中了。

两个方法的关键代码分别如下

1
2
3
return JdbcType.forCode(rsmd.getColumnType(columnIndex));

return Resources.classForName(rsmd.getColumnClassName(columnIndex));

JdbcType

com.mysql.jdbc.ResultSetMetaData 实现了 java.sql.ResultSetMetaData 接口,此处我们调用了其中的

1
2
3
public int getColumnType(int column) throws SQLException {
return getField(column).getSQLType();
}

序号的作用就体现出来了,根据序号找到此 field ,再找其 SQLType ,根据 SQLType 去JdbcType 类 (enum类型) 内查找对应的 jdbcType。

JdbcType 类内维护了一个 map 类型静态变量 codeLookup ,类加载时为 codeLookup 添加了39个元素,key 值其实就是 SQLType , int 类型,value 就是本 jdbcType 。

因此根据 SQLType 在此处就直接能毫不费力地找出对应的 jdbcType。

1
2
3
4
5
static {
for (JdbcType type : JdbcType.values()) {
codeLookup.put(type.TYPE_CODE, type);
}
}

javaType

同样需要先找到 SQLType ,以及field 内的另外几个属性值,例如 isUnsigned 等一起进入 getClassNameForJavaType 方法找到对应类名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static String getClassNameForJavaType(int javaType, boolean isUnsigned, int mysqlTypeIfKnown, boolean isBinaryOrBlob, boolean isOpaqueBinary,
boolean treatYearAsDate) {
switch (javaType) {
case Types.BIT:
case Types.BOOLEAN:
return "java.lang.Boolean";
case Types.TINYINT:
if (isUnsigned) {
return "java.lang.Integer";
}
return "java.lang.Integer";
//......大量 case
default:
return "java.lang.Object";

而这里的大量的 swtich-case 终于算是解除了我们一部分的疑惑了!底层终归还是通过 swtich-case 这种最原始的操作来把 jdbcType 映射到 java 类里去的!

奇怪的现象

上面说到,如果没序号,则直接返回 ObjectTypeHandler。

序号是getColumnType(int column) 用于找到到对应的 field 的关键属性。

那么为什么有可能会没有序号呢?

通过 debug 我们发现,明明 fields 内有 8 个元素,每个字段的原始名和别名都清清楚楚,到 columnIndexLookup 里居然只剩下 6 个? 很显然,问题出在了

1
String name = rsmd.getColumnName(i);

中间有几次取出了重复的 name 。为什么会有重复的 name 呢,我们进入getColumnName 一探究竟。

1
2
3
4
5
6
7
8
9
10
11
12
13
public String getColumnName(int column) throws SQLException {
if (this.useOldAliasBehavior) {
//如果设置了使用别名的属性,就获取此field的别名。
return getField(column).getName();
}
//获取此field的name属性,而不是别名。
String name = getField(column).getNameNoAliases();
if (name != null && name.length() == 0) {
//如果连原始字段名都找不到,就还是获取别名
return getField(column).getName();
}
return name;
}

实际debug发现,我们每次都是通过 getNameNoAliases 找到 name 的。再底层的代码就不需要贴了,看到这里我们就明白了。这里的 name 其实是每个字段的数据库内的字段名,而不是我们定义的别名,所以才会出现重复的情况,比如 user 表有 id 字段,address 表同样会有 id 字段。

总结

今天我们分析完了类型转换器,发现对于未知的数据类型,有一部分是通过ObjectTypeHandler 解析,其底层的用了com.mysql.jdbc.ResultSetImpl.getObject 内的依据 Field.SQLType 的 swtich-case 。

另一部分则是通过UnknownTypeHandler 去查找合适的解析器来解析。

关于查找解析器的步骤,我们将进入下一层级TypeHandlerRegistry 来继续学习。

评论和共享

  • 第 1 页 共 1 页
作者的图片

heeexy

世上是不是就没有你不认识的字了?


JAVA


南京