问题

Service层注入Dao时, Intellij 总会以红色波浪线提示我们

1
2
@Autowired
private UserDao userDao;

Could not autowire. No beans of ‘UserDao’ type found.
Checks autowiring problems in a bean class.

尽管我们都知道 Dao 层的 Bean 实际上都是有的,并且可以设置关闭这恼人的提示,但是我们有没有想过为什么 Intellij 就找不到这个 Bean 呢?甚至有人有这种做法

1
2
3
@Repository
public interface UserDao {
}

来避免提示,但是这种做法正确么?

所以今天我们的疑问就是

  1. 为什么 Dao 层不需要加 @Repository 注解,源码里到底做了什么?
  2. 加了 @Repository 注解有什么影响?

答案

  1. 关键在于 ClassPathMapperScanner 对指定包的扫描,并且扫描过程对 Spring 原本的扫描 Bean 的步骤 “加了料” ,Spring 本身只扫实现类,但 MyBatis 的扫描器扫了接口 。并且扫完接口之后,为接口配了个 BeanDefinition ,并且这个 bd 的 BeanClass 是 MapperFactoryBean

    对于 BeanDefinition 和 MapperFactoryBean 不了解的同学请查询相关资料和源码

  2. 仅仅只能解决 Intellij 静态查找 bean 的问题,没有实际作用。即使加了注解,比如@Controller,@Service 等等,也会被 Spring 的扫描器给忽略掉,因为扫描器会过滤掉接口

源码探索

下面的源码部分如果读者提前有 MyBatis 的 Bean 的执行流程,和 Spring 的 Bean加载的相关知识就更好理解。

1. 分析问题

关于为什么不需要注解就能获取到 Dao 层的 Bean,看似答案很简单,因为配置了扫描指定这个包里的 xxxDao.class 啊,比如使用注解 @MapperScan(“com.example.dao”)。

这个答案太过表面,觉得问题简单只是因为对 Spring 的 Bean 不熟悉。

我们何时见过 @Component 及其衍生的3个注解 @Controller、@Service、@Repository 加在接口上面的?

自己测试新建个接口,上面加注解,然后找个 Controller 里 @Autowired 注入一下,项目立马会报错 NoSuchBeanDefinitionException 。

2. 切入源码

切入点

既然使用注解 @MapperScan 就好使,那么我们就从这个点切入源码看一下,先找出源码中何处用了此注解,非常幸运的是,只有一处用到了此注解 :MapperScannerRegistrar.registerBeanDefinitions() 。

并且从类名和方法名就可以很清楚的看出这个类的功能是扫描 Mapper 并注册,方法的功能就是注册 BeanDefinitions 到 Spring 中。方法的源码我就不贴了,很容易看出来是创建一个扫描器 ClassPathMapperScanner ,设置好一系列属性比如 Spring 的注册表之后,执行 doScan() 方法去扫描 @MapperScan 提供的包。

doScan() 扫描资源,转换为 BeanDefinition

doScan() 方法也很简单,就是两步:

  1. 调用父类 ClassPathBeanDefinitionScanner 的doScan()方法,也就是 Spring 扫描BeanDefinition 的方法。过程不是很重要,我们需要知道这个扫描方法的一个关键就是
1
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

在其中对所有的候选者使用 isCandidateComponent() 方法判断是否为符合要求的 BeanDefinition。

1
2
3
4
5
6
7
8
9
10
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return isConditionMatch(metadataReader);
}
}

这有两组过滤器来过滤扫描到的资源。Spring 默认的过滤器是排除掉抽象类/接口的。而MyBatis 的扫描器重新注册了过滤器,默认对接口放行。

其实还有一些其它的过滤要求,但是不影响我们本问题的探究,所以不深入解读了。

源码读到这里,我们先找到了本文的第二个问题的答案。也就是 Spring 会忽略掉接口上面的注解,不会添加它进入 BeanDefiniiton ,也就难怪测试的时候会抛出 NoSuchBeanDefinitionException 的异常了。而 MyBatis 则会把这些接口拉过来注册BD 。

对 BeanDefinition 的加工

读到这里我们可能有了更大的疑问,拿接口注册 BeanDefinition ,那获取 Bean 的时候如何去实例化这个对象啊?接口可是不能实例化出对象的啊,而且我们也没有做实现。

原来是 MyBatis 的扫描器在调用完父类的扫描方法后,对 BeanDefinition 进行了加工 processBeanDefinitions() 。其中最关键的两行代码是

1
2
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 
definition.setBeanClass(this.mapperFactoryBean.getClass());

第一行,我们发现把这个接口的类名塞到了构造器参数中

小彩蛋,这里塞的是 String ,而我们的构造器参数其实要的是 Class 。但是 Spring 的 ConstructorResolver.autowireConstructor 中用到了 Object[] argsToUse 去做了个转换 。

第二行,beanDefinition 的 BeanClass 被设置成了 MapperFactoryBean !

熟悉 Spring 和 MyBatis 的读者肯定一下就明白了,就是这个地方进行了”偷梁换柱”!

1
2
@Autowired
private UserDao userDao;

还是拿 UserDao 为例,我们向 Spring 容器说 “给我来个 UserDao 的实例”,而 Spring 根据注册时候的 BeanDefinition ,去工厂( MapperFactoryBean )里面扔了个 UserDao.class 的参数进去,工厂的 getObject() 方法给我们返回了它制造的 userDao 。

就这样,我们没有去写实现类,轻轻松松拿到了我们需要的 userDao 。

至于 MapperFactoryBean 里做了什么返回了 userDao 出来?其实就是它的 getObject 方法返回的是 DefaultSqlSession.getMapper(Class type)方法,返回的是 MapperProxy 代理的类,而这个代理类的 invoke 方法并不像我们平时见到的代理中的 invoke 方法一样调用原始目标的 method.invoke ,而是去找 MapperMethod 执行了。

收获

这次的源码探究下来,收获的不仅仅是了解了 Dao 层 Bean 的注入,更是串起了我们最常用的 Spring 和 MyBatis ,换句话说,我们打通了从 Service 层到 Dao 层。

在以往 Debug 代码时看到的 MapperProxy,MapperMethod,我们清楚了这是从何而来,也对 MyBatis 中代理的巧妙运用更加熟悉。

参考文献

https://blog.csdn.net/java280580332/article/details/72123890

https://blog.csdn.net/mingtian625/article/details/47684271

评论和共享

从网上看到一篇博文 徒手撸框架–实现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 注入。

评论和共享

​ MyBatis 想要打印日志,时不时想要来句 log.debug() 、log.error() ,需要个打印机 ,可是自己又不想去实现(而且跟着整个项目用同样的打印系统才是王道啊),需要去用别人家的打印机,要用别人家的产品啊,那问题可就来了。

问题 1

​ 市面上各家的打印机 slf4j、java.util.logging、log4j 甚至 System.out 都是各种不同的用法,这使用起来就太麻烦了。

思路

​ 不管市面上打印机有多少型号,我家 MyBatis 包里的类只用自家的统一接口,我家的类只管 log.debug()、log.error()…

​ 定义好了接口,就需要实现类 impl 来实现这些 debug()、error() 方法了 , 咱假装是自己来实现,其实去调用真正打印机 slf4、log4j 的方法,这样就把别人家的打印机和咱自家的接口关联起来啦。

​ 这就是适配器模式

​ 我家的每个实现类其实就是一个适配器,每个适配器去适配一种打印机。比如 slf4jLoggerImpl 就完成了对 slf4j 打印机的适配,slf4jLoggerImpl .debug() 调用了slf4j.Logger.debug()。

​ 这样市面上每多一种打印机,比如想用 log4j2 了,我就只需要加一种适配器 log4j2LoggerImpl 去适配它就可以了。

代码

​ 定义接口

1
2
3
4
5
public interface Log {
boolean isDebugEnabled();
void debug(String s);
...
}

​ 在实现类里完成适配,比如适配 slf4j 的 Slf4jLoggerImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.apache.ibatis.logging.Log;
import org.slf4j.Logger;

class Slf4jLoggerImpl implements Log {
private final Logger log;

//注意这个地方,入参为 org.slf4j.Logger
//说明咱这个适配器要用起来,是需要传入一个真正的 slf4j 家的打印机进来的
public Slf4jLoggerImpl(Logger logger) {
log = logger;
}

//看似外面调用咱 MyBatis 的 Log 的 debug()
//其实是调用 org.slf4j.Logger 的 debug()
@Override
public void debug(String s) {
log.debug(s);
}
}

问题 2

​ 适配器咱是做好了,slf4j、log4j、stdout…做了那么多适配器,可是怎么用啊?我真正要打印机的时候,怎么才能知道我该调用哪款适配器啊?难道每次取打印机的时候,都去查一遍咱的系统配了那款打印机(jar) 包么?而且以后多加了1种打印机,我还去每个地方都改一遍,都多加一道判断么?

思路

​ 这其实就是获取实例的时候的问题,咱获取实例太累了,不如来个统一的工厂吧,我每次想打印的时候,都去找工厂要一台打印机,你工厂按照我 Log 的接口给我一个实例就是了。我也不管你给的具体实现到底是 slf4j 家的,还是 log4j 的,甚至可能是 NoLoggingImpl 每次调接口都不处理的这种空壳打印机。

​ 这样一来,有了统一的工厂,判断系统用哪种适配器的任务就可以在工厂完成了。

​ 而且以后就算新加了打印机,也只要改改工厂的代码,在工厂里多加一重判断就可以了。

​ 这就是工厂模式

代码

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
public final class LogFactory {
private static Constructor<? extends Log> logConstructor;
static {
tryImplementation(new Runnable() {
@Override
public void run() {
useSlf4jLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useCommonsLogging();
}
});
}

private LogFactory() {
// disable construction
}
public static Log getLog(String logger) {
try {
return logConstructor.newInstance(logger);
} catch (Throwable t) {
}
}
}

​ LogFactory 提供了对外的 getLog(String logger) 方法,给需要 logger 的地方提供一个 Log 的实例。

​ 内部的实现依靠 logConstructor 这个构造器通过反射来实例化一个 Log 的 Impl ,也就是之前的适配器,比如 Slf4jLoggerImpl 。

​ 判断采用哪种logConstructor 的任务则在类初始化的时候就执行了,依次尝试了我们的每一款适配器,碰上报错就说明没 jar 包,下一款,全都没有的话,就用 NoLoggingImpl 了。

评论和共享

前后端分离实践小结

发布在 小结

背景

​ 公司项目转型,要开新的运营管理平台,我提议借此开新项目的机会,进行前后端分离,由我负责带领小组新技术学习/分享,探坑填坑.

分离原因

  1. 最关键一点,受不了jsp/freemarker里一塌糊涂的代码,分离之后,代码按规范写,简洁,好管理
  2. 原本我们后端就一直在给Android/iOS提供接口,本次分离,可以方便以后H5端的项目重构,实现Android+iOS+H5的统一,一套接口可以供三端同时使用,大量节省工作量,也可以更好地保证公司产品质量的统一性.
  3. 方便后端专心处理数据,前端实现页面效果.而不是前端仅仅画页面写css,后端还需要复制粘贴过来,套数据,修改已有页面时,前端更是不方便插手.

技术选型

​ 组员后端技术都是SSM,前端都只是略接触过AngularJS/Vue.

​ 我之前接触了一下Spring Boot,感觉配置很清爽,搭框架轻松,业务写起来快,可以让组员无缝切过来,无需费时学习,因此后端框架选定Spring Boot.而登录控制与权限管理一直是公司以前几个项目的弱项,因此本次决定引入shiro,而组员无一对shiro熟悉的,研究shiro的任务自然由我承担.

​ 前端框架是本次前后端分离的重点,由于我们组无人熟悉前端的新框架,所以这次势必每个人都需要学习许多新的前端知识.我拿公司的H5项目的几个页面进行过搭建vue框架重构练手,认为vue文档清晰,资料丰富,相关开源方案够多,我们这次可以放心学习,使用.(当然最主要的还是因为坑全都得由我来填,得挑个熟悉的)

目前进展

​ 后端:Spring Boot框架搭建完成,shiro可以进行权限管理,自定义拦截器,常用工具类完成,对常用的增删改查,返回结果,异常处理都可以快速搞定.

​ 前端:数据交互封装完毕,路由熟悉,前端权限管理初步熟悉,选用饿了么开源的Element框架,常用的增删改查的页面元素及工具方法都已让大家掌握.

​ 本周一开始教组员使用Intellij IDEA,教前端快速铺页面的方法,介绍前后端我封装的各种小轮子.经过一周时间,全部都已熟悉这套新的前后端框架,每个人都可以独立快速地推出常用页面.

接口端小技巧

​ 因为后端全部返回统一json格式的接口,所以我设计了一些小的工具方法,方便快速推出新接口,节省重复代码.

  1. 使用JSONObject而不是实体类

    ​ 因为后端业务不算复杂,所以舍弃了实体类的语义性,转而使用阿里的fastjson的JSONObject接收MyBatis返回的结果.比如一个简单的查询只需要

    1
    2
    3
    4
    5
    6
    7
    8
    <select id="getSimple" resultType="com.alibaba.fastjson.JSONObject">
    SELECT
    s.order_id shopOrderId,
    s.operation operation,
    date_format(s.create_time, '%Y.%m.%d %H:%i:%s') createTime
    FROM shop_order_log s
    WHERE s.user_delete_status = "1"
    </select>

    就可以将三个字段shopOrderId,operation,createTime放入json内,字段名称更加灵活,也免去了大量的建实体类,写resultMap的代码.

    ​ 更好的一点是,不使用实体类,可以在返回结果中避免掉很多空的需不要的字段.

    ​ 还有,因为所有的接口层都返回JSONObject,所以编写工具方法,快速返回成功/失败结果也很简单,我写了多个工具方法,包括入参转json,入参非空校验,返回成功/失败结果,分页,这里就不一一贴出代码了.

  2. 自定义Exception

    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
    public class CommonJsonException extends RuntimeException {
    private JSONObject resultJson;

    /**
    * 调用时可以在任何代码处直接throws这个Exception,
    * 都会统一被拦截,并封装好json返回给前台
    *
    * @param errorEnum 以错误的ErrorEnum做参数
    */
    public CommonJsonException(ErrorEnum errorEnum) {
    JSONObject jsonObject = new JSONObject();
    CommonUtil.returnJson(jsonObject, errorEnum);
    this.resultJson = jsonObject;
    }

    public CommonJsonException(JSONObject resultJson) {
    this.resultJson = resultJson;
    }

    public JSONObject getResultJson() {
    return resultJson;
    }
    }

    @ControllerAdvice
    @ResponseBody
    public class GlobalExceptionHandler {
    @ExceptionHandler(CommonJsonException.class)
    public JSONObject CommonJsonExceptionHandler(CommonJsonException commonJsonException) throws Exception {
    return commonJsonException.getResultJson();
    }
    }

    在需要返回给前端错误代码编号时,比如校验参数非空,校验手机号失败,可以直接抛出此异常,经错误拦截器拦截到此异常后,可以直接返回错误码给前端,节省大量的判断/返回的代码.这一点也是前阵子从Spring源码中学到的.

存在的问题

  1. 近期主要问题基本都是待我去研究深入的技术,比如shiro的动态权限与vue-router的异步路由的结合,vue组件的通信,vuex的状态管理
  2. 其次是组员需要对vue更加熟悉,我这次要求了大家抛弃jQuery,用数据绑定的思想去写前端.目前大家的前端水平也还只是可以快速复制粘贴出页面,对于复杂的页面和组件,甚至对我们前端项目的目录结构与各部分功能,都不算掌握.
  3. 目前我们所谓的前后端分离其实还只是初步的业务和代码上的分离,以后如果要重构H5项目,可能还需要开NodeJS项目来解决和Android/iOS一样的接口加密问题,统一session管理问题,以及更多的前后端分离可能给我们带来的问题.

评论和共享

​ 最近在做前后端分离的新框架,选用了后端springboot+shiro,前端vue+elementUI,第一次搭SSM之外的非demo项目,尤其shiro更是之前从未接触,折腾了很多天,遇到很多问题,大部分能百度出来,剩下的非常费时的问题且称之为坑吧.

跨域

​ 一大部分问题就是跨域造成的,本身vue-cli搭建的项目可以用

1
2
3
4
5
6
7
8
9
10
11
// 1. axios的baseURL设置为/api
// 2. 如下设置
proxyTable: {
'/api': {
target: 'http://127.0.0.1:8080/',
changeOrigin: true,
pathRewrite: {
'^/api': '/'
}
}
},

来解决开发环境跨域的问题,生产环境反正可以打包静态文件到springboot项目中直接跑.

而我还是脑抽得选择了强行跨域,假装自己要把静态文件单独放个服务器跑…为此遇到很多问题,折腾好几天.

因此强力推荐上面的方案,简单快捷地解决跨域!

如果生产环境真的需要跨域的话,再按下面方法设置

大部分跨域的配置都能百度搜出来:

  1. axios要 withCredentials: true

  2. 用session而不用啥特殊的token之类的话,就不用 config.headers['X-Token'] = getToken() ,这句是vueAdmin-template 中带的,需要删掉.

  3. 后端需要配置允许跨域

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Configuration
    public class CorsConfig {
    private CorsConfiguration buildConfig() {
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址
    corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
    corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
    corsConfiguration.setAllowCredentials(true);
    return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", buildConfig()); // 4 对接口配置跨域设置
    return new CorsFilter(source);
    }
    }

shiro拦截ajax返回json

​ shiro拦截到需要登录而用户尚未的请求时,会重定向至 /login/auth (未配置时是login.jsp),而ajax是不允许重定向的,ajax会收到302错误码,报错

Failed to load http://localhost:8080/test: Redirect from ‘http://localhost:8080/test‘ to ‘http://localhost:8080/login/auth‘ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:9528‘ is therefore not allowed access.

因此我们需要对拦截器进行改造,不要它默认的重定向了,我们直接去response里把json写好输出给前端.

自定义filter,(因为我的后端全部返回json,所以这里不判断是否ajax了)

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
public class AjaxPermissionsAuthorizationFilter extends FormAuthenticationFilter {

@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("returnMsg", "未登录或登录已失效");
if (this.isLoginRequest(request, response)) {
if (this.isLoginSubmission(request, response)) {
return this.executeLogin(request, response);
} else {
return true;
}
} else {
PrintWriter out = null;
HttpServletResponse res = (HttpServletResponse) response;
//下面这几行也是后面要讲的坑, 这里的Access-Control-Allow-Origin 设置为*的话,前端还是会报错.
res.setHeader("Access-Control-Allow-Origin", "http://localhost:9528");
// response1.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Credentials", "true");
try {
res.setCharacterEncoding("UTF-8");//设置编码
res.setContentType("application/json");//设置返回类型
out = response.getWriter();
out.println(jsonObject);//输出
} catch (Exception e) {
} finally {
if (null != out) {
out.flush();
out.close();
}
}
return false;
}
}
}

接下来就是最坑的地方了,拦截器的注入.

原本搜到的方法是在ShiroConfiguration类中注入.

1
2
3
4
5
6
7
8
9
@Bean(name = "ajaxPermissionsAuthorizationFilter")
public AjaxPermissionsAuthorizationFilter ajaxPermissionsAuthorizationFilter(){
return new AjaxPermissionsAuthorizationFilter();
}

//然后shiroFilterFactoryBean设置过滤器
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("authc", new AjaxPermissionsAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap);

结果自定义的拦截器把所有的请求都拦截了,直接无视了我设置的

1
2
3
4
5
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login/**", "anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/**", "authc");

各种百度,能搜到的相关的资料也就只有segmentfault的提问没解决,题主给我的回复也不能解决 , 百度知道提问的没解决 , 简书方案并不能解决 .

最终在某个百度结果的第三页找到这篇博客 ,博主对问题一步一步的排查分析,debug源码,最终知道

Springboot 先加载了我们自定义的 Filter,然后再加载了 ShiroFilter

解决方法:
在自定义的filter里加上下面的代码

1
2
3
4
5
6
@Bean
public FilterRegistrationBean registration(自定义Filter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}

在configration类里就不需要声明这个bean,只需要直接调用 filterMap.put("authc", new 自定义Filter());

Access-Control-Allow-Origin

在上面的自定义filter里,

1
res.setHeader("Access-Control-Allow-Origin", "http://localhost:9528");

如果设置为*的话,前端虽然可以收到json,但还是会报错

Failed to load http://localhost:8080/test: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. Origin ‘http://localhost:9528‘ is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

不允许设置为通配符* .

虽然此处设置了”http://localhost:9528“ 但是这种做法终究不合适.

百度继续搜到此博客

得到解决方法

1
res.setHeader("Access-Control-Allow-Origin", ((HttpServletRequest) request).getHeader("Origin"));

评论和共享

获取单例bean getSingleton

明明之前一篇已经讲过了获取单例,为什么这里又是获取单例bean呢?

两天不看书,果断又忘了.前面学的是从缓存中获取,这里是真正的获取.

DefaultSingletonBeanRegistry 中重载此方法,第二参数为ObjectFactory<?>

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
67
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
//这里再次看到了这个singletonObjects,上篇文章介绍过,是维护了单例对象
//书中说法是:用于保存BeanName和创建bean实例之间的关系
Map var3 = this.singletonObjects;
synchronized(this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
//首先获取一遍,如果不存在,才去创建
if (singletonObject == null) {
//工厂如果正在销毁,这时候获取bean就会报错
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while the singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)");
}

if (this.logger.isDebugEnabled()) {
//打印日志,正在创建单例bean(beanName)的共享的实例
this.logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
//标志这个beanName正在创建,如果同时重复创建,会报错
this.beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = this.suppressedExceptions == null;
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet();
}

try {
//=============这里应该是最关键的创建bean的步骤===============
singletonObject = singletonFactory.getObject();
//标识新创建出来的单例
newSingleton = true;
} catch (IllegalStateException var16) {
//非法状态错误,应该是创建过程中发现存在其他线程已创建此单例
//所以此处catch里面再次调用了singletonObjects.get(beanName);
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw var16;
}
} catch (BeanCreationException var17) {
//其它创建bean错误
BeanCreationException ex = var17;
if (recordSuppressedExceptions) {
Iterator var8 = this.suppressedExceptions.iterator();
while(var8.hasNext()) {
Exception suppressedException = (Exception)var8.next();
//把其它recordSuppressedExceptions塞入这个ex一并抛出
ex.addRelatedCause(suppressedException);
}
}
throw ex;
} finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
//确认这个beanName在几个set中的状态
this.afterSingletonCreation(beanName);
}
if (newSingleton) {
//如果是新创建的,则加入缓存,移除几个新创建的标识,名字加入registeredSingletons
this.addSingleton(beanName, singletonObject);
}
}
//返回,如果是NULL_OBJECT也作为null返回.
//这个NULL_OBJECT应该是在前面的获取方法中有可能的特殊返回值
return singletonObject != NULL_OBJECT ? singletonObject : null;
}
}

从上可以看出,获取单例还是通过 synchronized(this.singletonObjects) 加锁来实现,先从 singletonObjects 查一遍有没有已存在的,若没有则再进行创建。

而创建的步骤,则是在入参的 ObjectFactory<?> 中完成,调用其 getObject() 依然还是调用我们本身AbstractBeanFactorycreateBean 方法。


我们先不急着往下看,先理一理,首先第一点要知道的是单例加锁都是 synchronized(this.singletonObjects) 。其次获取单例 bean 的大致步骤,无非就是:

  1. 上一文中介绍的,从缓存中拿,并且允许“早期引用” ,即从 earlySingletonObjects 中拿。

  2. 如果缓存中没有,则自己创建,

    2.1 在各种 map 里记录它的 创建、销毁等信息

    2.2 通过createBean 方法去具体创建这个实例。

评论和共享

作者的图片

heeexy

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


JAVA


南京