获取单例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 方法去具体创建这个实例。

评论和共享

缓存中获取单例bean getSingleton

DefaultSingletonBeanRegistry 中实现此方法

1
2
3
4
5
6
7
8
//这个map维护了单例对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//当前正在创建中的单例对象
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
//早期单例对象
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
//单例工厂?
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
//如果根据这个beanName没取到对象,但发现这个对象还在创建中.....
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
Map var4 = this.singletonObjects;
synchronized(this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
//如果早期单例对象中没有此bean,并且允许早期依赖
if (singletonObject == null && allowEarlyReference) {
//当某些方法需要提前初始化的时候则会调用 addSingletonFactory方法将对应的ObjectFactory初始化策略存储在singletonFactories
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//调用预先设定的getObject方法
singletonObject = singletonFactory.getObject();
//记录在缓存中,earlySingletonObjects 和 singletonFactories互斥
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject != NULL_OBJECT ? singletonObject : null;
}

这里还是看得迷迷糊糊的,毕竟一来就各种取值, 往后看看在哪塞值的吧!

从bean的实例中获取对象 getObjectForBeanInstance

​ 我们得到bean的实例后要做的第一步都是调用这个方法来检测一下正确性,其实就是用于检测当前bean是否是FactoryBean 类型的bean,如果是,就要调用getObject() 方法作为返回值

FactoryBean 在之前我们便已经接触过.

getObjectForBeanInstance 方法就不贴了, 主要就是各种验证, 取缓存等等, 里面需要注释的一段如下

1
2
3
4
5
   	//将存储XML配置文件的GernericBeanDefinition 转换为RootBeanDefinition,
//如果指定BeanName是子bean的话,同时会合并父类的相关属性
if (mbd == null && this.containsBeanDefinition(beanName)) {
mbd = this.getMergedLocalBeanDefinition(beanName);
}

​ 方法中的关键是 getObjectFromFactoryBean , 实现类为 FactoryBeanRegistrySupport

​ 而 getObjectFromFactoryBean 方法中也还不是最核心的, 其代码主要是保证单例bean的全局唯一. 如果是单例,那就不用重复创建,可以使用缓存来提高性能. 另外方法还根据需要对bean进行了后处理postProcessObjectFromFactoryBean()

后处理即是遍历所有的bean后处理器,在bean初始化后调用它的处理方法,后面还会详细学习.

​ 它的关键是 doGetObjectFromFactoryBean ,方法名前面多加了个do…

在这个方法里面,我们终于看到想要看到的 `object = factory.getObject();`  ,虽然早已知道必须要走到这,但是到这一步`getObject()` 还是绕了很多路,包括到这里居然还使用了`java.security.AccessController` 权限验证...

评论和共享

1
2
3
4
5
6
7
8
9
10
package org.springframework.beans.factory;

public interface FactoryBean<T> {
//返回由FactoryBean创建的bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中
T getObject() throws Exception;
//返回FactoryBean创建的bean类型
Class<?> getObjectType();
//返回bean实例的作用域是singleton还是prototype
boolean isSingleton();
}

​ 当配置文件的class属性配置的实现类是FactoryBean 时,通过getBean() 方法返回的不是FactoryBean 本身,而是FactoryBean.getObject() 方法所返回的对象.

​ 相当于FactoryBean.getObject() 代理了getBean() 方法.

​ 例如,如果使用传统方式配置Car的bean,Car的每个属性对应一个 元素标签

1
2
3
4
5
6
public class Car {
private int maxSpeed;
private String brand;
private double price;
//get/set
}

​ 如果使用FactoryBean的方式就会灵活一些,下面通过逗号分隔符的方式一次性地为Car的所有属性指定配置值:

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
public class CarFactoryBean implements FactoryBean<Car> {
private String carInfo;

@Override
public Car getObject() throws Exception {
Car car = new Car();
String[] infos = carInfo.split(",");
car.setBrand(infos[0]);
car.setMaxSpeed(Integer.valueOf(infos[1]));
car.setPrice(Double.valueOf(infos[2]));
return car;
}

@Override
public Class<?> getObjectType() {
return Car.class;
}

@Override
public boolean isSingleton() {
return false;
}

public String getCarInfo() {
return carInfo;
}

public void setCarInfo(String carInfo) {
this.carInfo = carInfo;
}
}

有了这个CarFactoryBean之后,就可以在配置文件使用下面这种自定义的配置方式配置Car bean了

1
<bean id="car" class="com.test.factoryBean.CarFactoryBean" carInfo="超级跑车,400,3000" />

调用 getBean(“car”) , Spring并不会返回CarFactoryBean 而是返回car

如果希望返回CarFactoryBean 那就 getBean(“&car”)


书本作者讲到FactoryBean 接口对于Spring框架很关键, Spring自身就提供了70多个实现.

它们隐藏了实例化一些复杂的bean的细节,给上层应用带来了便利.

疑问: 具体哪些地方用FactoryBean 带来了真正的便利呢?

答:

评论和共享

本文要看啥?

​ 前面已经了解了bean从配置文件到解析成BDHolder到注册的流程,我们已经将bean的信息封装好,塞入了map中,这个map可能在不同的实现里面,前面重点学的就是DefaultListableBeanFactory里的beanDefinitionMap.

​ 我们现在要探索bean的加载,围绕最初的示例代码:

1
MyTestBean bean =(MyTestBean)bf.getBean("myTestBean");

​ BeanFactory是个接口,其下实现关系很复杂, getBean方法的实现主要是在AbstractBeanFactory 这一层.

​ 本文就是先来快速体验一下加载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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
protected <T> T doGetBean(String name, Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
//转换beanName
final String beanName = this.transformedBeanName(name);
//根据beanName找其单例
Object sharedInstance = this.getSingleton(beanName);
Object bean;
if (sharedInstance != null && args == null) {
if (this.logger.isDebugEnabled()) {
if (this.isSingletonCurrentlyInCreation(beanName)) {
this.logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
} else {
this.logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
//返回对应的实例
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
} else {
//原型模式下,如果这个bean已经正在创建中,说明是如下情况
//A中有B的属性,B中有A的属性,当依赖注入的时候,就回产生当A还未创建完的时候,
//因为对于B的创建,再次返回创建A,造成循环依赖
//所以就会报错
if (this.isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
//获取父工厂
BeanFactory parentBeanFactory = this.getParentBeanFactory();
//如果本工厂没有这个bean,存在父工厂,就去父工厂找
if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
String nameToLookup = this.originalBeanName(name);
//注意下面是递归地去父工厂找
if (args != null) {
return parentBeanFactory.getBean(nameToLookup, args);
}
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
//如果不是仅仅做类检查则是创建bean,这里要进行记录
if (!typeCheckOnly) {
this.markBeanAsCreated(beanName);
}
try {
final RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
this.checkMergedBeanDefinition(mbd, beanName, args);
String[] dependsOn = mbd.getDependsOn();
String[] var11;
//如果存在依赖,就递归地先去实例化依赖的bean
if (dependsOn != null) {
var11 = dependsOn;
int var12 = dependsOn.length;

for(int var13 = 0; var13 < var12; ++var13) {
String dep = var11[var13];
if (this.isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
this.registerDependentBean(dep, beanName);
this.getBean(dep);
}
}
if (mbd.isSingleton()) {
//单例模式的创建
sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
return AbstractBeanFactory.this.createBean(beanName, mbd, args);
} catch (BeansException var2) {
AbstractBeanFactory.this.destroySingleton(beanName);
throw var2;
}
}
});
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
//原型模式的创建
var11 = null;
Object prototypeInstance;
try {
//创建原型之前,先把prototypesCurrentlyInCreation里塞值,表示当前正在创建哪些原型
this.beforePrototypeCreation(beanName);
//注意到createBean这个方法留给了子类去实现
prototypeInstance = this.createBean(beanName, mbd, args);
} finally {
//移出prototypesCurrentlyInCreation,表示创建这个原型结束
this.afterPrototypeCreation(beanName);
}

bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
//其它模式的创建(指定的scope上实例化bean
String scopeName = mbd.getScope();
Scope scope = (Scope)this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}

try {
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
AbstractBeanFactory.this.beforePrototypeCreation(beanName);

Object var1;
try {
var1 = AbstractBeanFactory.this.createBean(beanName, mbd, args);
} finally {
AbstractBeanFactory.this.afterPrototypeCreation(beanName);
}
return var1;
}
});
bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException var21) {
throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", var21);
}
}
} catch (BeansException var23) {
this.cleanupAfterBeanCreationFailure(beanName);
throw var23;
}
}

if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
//检查类型是否符合bean的实际类型
try {
return this.getTypeConverter().convertIfNecessary(bean, requiredType);
} catch (TypeMismatchException var22) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", var22);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
} else {
return bean;
}
}

提炼大致步骤

这120多行的代码,Spring都并没有再进行重构,可见其复杂程度,辛亏有书本解释,了解到大致步骤:

  1. 转换对应的beanName,因为传入的参数可能是alias

    从其具体方法中看,是之前见过的, 处理& 和别名的递归寻找本名.

    去前面笔记中寻找,在 SimpleAliasRegistry 中已经研究过此方法

  2. 尝试从缓存中加载单例

    此时可能从缓存中取出的是还没创建好的bean,主要是因为防止循环依赖

  3. bean的实例化

    如果从缓存中得到了bean的原始状态,则需要对bean进行实例化.

  4. 原型模式的依赖检查

  5. 检测parentBeanFactory

  6. 将存储XML配置文件的GernericBeanDefinition转换为RootBeanDefinition

  7. 寻找依赖

    因为bean的初始化过程中很可能会用到某些属性,而某些属性很可能是动态配置的,并且配置成依赖于其他的bean,那么这个时候就有必要先加载依赖的bean.

  8. 针对不同的scope进行bean的创建

  9. 类型转换

    有可能有这种情况,返回的bean,是个String,但是requiredType传入的是Integer类型,那么这时候本步骤就会起作用了. Spring中提供了各种各样的转换器,用户也可以自己扩展转换器来满足需求

评论和共享

自定义标签的使用

主要内容都在书上,p80

关键的部分为

  • 创建XSD文件
  • 创建类 继承AbstractSingleBeanDefinitionParser , 其继承关系最主要是实现了BeanDefinitionParser 接口,顾名思义,可以用来解析bean.
  • 创建Handler文件, 继承自 NamespaceHandlerSupport ,目的是将上面的组件注册到Spring容器内
  • 编写Spring.handlers 和 Spring.schemas文件.

这样,自定义的配置就结束了.

Spring加载自定义bean的流程主要就是遇到自定义标签后就去Spring.handlers和Spring.schemas中取找对应的handler 和 XSD .从而可以拿到parser

而代码里的主要步骤为

  1. 拿到标签对应的命名空间
  2. 根据命名空间找到对应的handler
  3. 调用handler的parse方法

获取标签的命名空间

调用org.w3c.dom.Node中的getNamespaceURI()

提取自定义标签处理器

1
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

这个resolve方法是接口,由DefaultNamespaceHandlerResolver 默认命名空间解析器来实现,其实现里面的第一步便是

1
Map<String, Object> handlerMappings = this.getHandlerMappings();

原来handlerMappings 是个map,想必是在注册时肯定把解析器添加到这个map里面来,用namespaceUri做key,这样找的时候就很好找了.

取出结果如果是NamespaceHandler 便可以返回,往下读我们发现,之所以这么爽取出来就是handler,是因为已经做过这个解析,把找出的handler塞入了map,相当于是缓存了.

否则就是类名 className, 我们需要把它转为handler

1
2
3
4
5
6
7
8
9
10
11
12
13
//使用反射,将类路径转化为类
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
} else {
//初始化类
NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
//调用类自定义的init()方法,这是所有handler都必须实现的方法
namespaceHandler.init();
//塞入缓存
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}

设计模式–单例模式

this.getHandlerMappings(); 获取handlerMapping处使用了单例模式

声明为volatile

1
private volatile Map<String, Object> handlerMappings;

获取单例时使用双重检查锁定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized(this) {
if (this.handlerMappings == null) {
try {
//读取"META-INF/spring.handlers" 转为map
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}

Map<String, Object> handlerMappings = new ConcurrentHashMap(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
} catch (IOException var5) {
throw new IllegalStateException("Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", var5);
}
}
}
}

return this.handlerMappings;
}

上面的volatile非常关键,如果没有的话,在初始化对象设置handlerMapping指向内存空间 中间可能发生重排序,导致另外的线程拿到了handlerMapping的空间地址,但是其实还没有初始化完成.

标签解析

拿到解析器之后,就调用解析的parse方法,返回BeanDefinition

我们的自定义handler里面无需实现parse方法,在父类NamespaceHandlerSupport 中自有实现,

1
2
3
public BeanDefinition parse(Element element, ParserContext parserContext) {
return this.findParserForElement(element, parserContext).parse(element, parserContext);
}

而parse方法主要步骤就是调用自己实现类的parseInternal 方法,parseInternal 方法除了调用我们实现的doParse方法之外,首先会进行一系列的数据准备,包括对beanClass.scope.lazyInit等属性的准备

评论和共享

本文要看啥

之前我们看解析默认标签,从最核心的bean开始看的

1
2
3
4
5
6
7
8
9
10
11
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
this.doRegisterBeanDefinitions(ele);
}
}

现在主要就剩下alias和import啦, beans其实就是迭代解析.

alias标签的解析

为bean定义别名,除了之前我们接触过的

1
<bean id="testBean" name="aliasTestBean" class="com.test"/>

还可以直接用alias标签

1
<alias name="testBean" alias="aliasTestBean"/>

解析步骤即使不看源码也知道很简单,因为之前我们已经深入研究过了SimpleAliasRegistry

  1. 校验 name alias 两个属性不能为空
  2. this.getReaderContext().getRegistry().registerAlias(name, alias);

这个getRegistry() 然后registerAlias() 果然就是我们之前学过的SimpleAliasRegistry 里面的注册别名的方法.

impory标签的解析

import标签和bean标签一样是我们最常见的标签,对于分模块管理配置文件很关键.

解析步骤

  1. 取出并校验resource 属性作为地址location

  2. 将地址中的系统属性 如 “${user.dir}”

  3. 判断location是绝对路径还是相对路径

    1. 如果是,则

      1
      importCount = this.getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
    2. 否则

1
2
3
4
5
6
7
8
Resource relativeResource = this.getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
importCount = this.getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
} else {
String baseLocation = this.getReaderContext().getResource().getURL().toString();
importCount = this.getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
  1. 还是预留给拓展监听事件的位置.

关键步骤

判断是否绝对路径
1
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();

isUrl : 以 classpath*: 开头 或者 classpath: 开头 或者 可以new URL(resourceLocation); 而不报错

ResourceUtils.toURI(location): 替换路径中空格为%20 后 new URI ,然后拿absolute属性

加载bean

其实最后都是调用在第一篇博文中我们就看过的 loadBeanDefinitions 方法

评论和共享

test_volatile_object

发布在 并发

本文要看啥


先不细谈volatile的基本原理,在读(写)这篇文章时,都是假设我们已经粗略了解了一点volatile的原理和作用的,主要就是 “读写都走主内存,保证任意线程对这个变量的可见性

在查看spring源码的时候,注意到spring在处理并发的操作List时, 虽然对list使用了volatile, 然而向list里面添加元素时,用的还是新建一个list,复制全部旧值,增加新元素,然后将旧的list地址指向新的list.

1
2
3
4
List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;

这么麻烦的操作,第一反应就是,volatile修饰的list, 直接添加元素依然不安全么?

去网上搜了一下相关问题, 参考博文地址 ,发现不止list, 对象也是一样的.

本文就是要来用代码直观地看看volatile 到底有什么效果,怎么用才有效果.

开始代码吧


线程共享对象里的boolean

注意代码要以-server模式运行,强制虚拟机开启优化

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
public class VolatileObjectTest implements Runnable {
// 加上volatile 就可以正常结束While循环了
private ObjectA a;

public VolatileObjectTest(ObjectA a) {
this.a = a;
}

public void stop() {
a.setFlag(false);
}

@Override
public void run() {
long i = 0;
while (a.isFlag()) {
i++;
/**
注意这里的sysout,如果有调用的话,即使没有volatile,子线程也经常能拿到a.flag,
结合后面的测试,发现sysout 或者 sysout(a.isFlag())之前有"---"之类字符串
都可能让a去从主内存去获取值,影响我们测试的结果
所以测试的时候不要乱打sysout了,感兴趣的话可以自己去各种测试一遍
*/
// System.out.println();
}
System.out.println("子线程正常结束");
}

public static void main(String[] args) throws InterruptedException {
// 注意代码要以-server模式运行,强制虚拟机开启优化
// 如果启动的时候加上-server 参数则会 输出 Java HotSpot(TM) Server VM
System.out.println(System.getProperty("java.vm.name"));
VolatileObjectTest2 test = new VolatileObjectTest2(new ObjectA());
new Thread(test).start();
Thread.sleep(200);
test.stop();
Thread.sleep(200);
System.out.println("主线程结束");
}

static class ObjectA {
private boolean flag = true;

public boolean isFlag() {
return flag;
}

public void setFlag(boolean flag) {
this.flag = flag;
}
}
}

这个代码还是很简单,

主线程将a的flag改为false,

子线程能正常结束的话, 说明子线程里 a 的 flag值获取到了false,

不能正常结束的话, 说明子线程a一直都是用其本地内存里的flag值,一直都是true.

测试结果就是

  1. 有volatile 修饰的情况下, 子线程能拿到false值
  2. 没有volatile ,子线程无法正常结束

在初步了解volatile 的可见性的情况下, 我会觉得这个结果很正常, 觉得自己掌握了volatile , 但是我们继续往下看…

线程共享对象里的对象

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
public class VolatileObjectTest implements Runnable {
// 加上volatile 就可以正常结束While循环了
private volatile ObjectA a;

public VolatileObjectTest(ObjectA a) {
this.a = a;
}

public void stop() {
a.getObjectB().setFlag(false);
}

@Override
public void run() {
long i = 0;
ObjectB b = a.getObjectB();
while (b.isFlag()) {
i++;
}
System.out.println(b.isFlag());
System.out.println(a.getObjectB().isFlag());
System.out.println("子线程正常结束");
}

public static void main(String[] args) throws InterruptedException {
// 如果启动的时候加上-server 参数则会 输出 Java HotSpot(TM) Server VM
System.out.println(System.getProperty("java.vm.name"));
VolatileObjectTest test = new VolatileObjectTest(new ObjectA());
new Thread(test).start();
Thread.sleep(200);
test.stop();
Thread.sleep(200);
System.out.println("主线程结束");
}

static class ObjectA {
private boolean flag = true;
private ObjectB objectB = new ObjectB();

public boolean isFlag() {
return flag;
}

public void setFlag(boolean flag) {
this.flag = flag;
}

public ObjectB getObjectB() {
return objectB;
}
}

static class ObjectB {
private boolean flag = true;

public boolean isFlag() {
return flag;
}

public void setFlag(boolean flag) {
this.flag = flag;
}
}
}

这次在ObjectA内添加了一个成员变量ObjectB,我们在子线程中跳出循环需要ObjectB中的flag变为false;

实际测试时发现:

  1. 无论ObjectA前有没有 volatile, 调用stop()方法都并不能正确终止子线程
  2. 成员变量ObjectB前添加volatile,同样不能正确终止子线程
  3. ObjectB的flag前加volatile,可以终止子线程 (这是当然的啦…)
  4. 如果循环里用的是 while (a.getObjectB().isFlag()) , ObjectA前又有volatile的话, 这样还是可以终止子线程.

测到这里,感觉这个博主给的例子是不是有问题啊,

1
2
3
4
ObjectB b = a.getObjectB();
while (b.isFlag()) {
i++;
}

问题是出在这里提前从a里面取出了b么,b已经指向不同的内存地址了么?不应该吧…

稍加思考,第三种结果里,b.flag用volatile修饰后,就可以正常退出,说明b还是指向的a里面的b的地址啊,没毛病啊.

突然感觉更晕了,把原博的评论翻到底,发现还真有人评论到这个,从评论里又学到了很多东西.

原博评论区的解疑

Q: 为什么sysout影响结果?

A: 如果在循环体内加一些语句,比如sysout或者new对象之类的稍微复杂而耗时的操作,就会发现就算没有volatile,线程同样可能被正常中断.因为经过高耗时操作之后,CPU会”怀疑人生”,单心自己对b.flag的缓存不是最新的,而去从主存获取.在这种情况下,线程会结束,只不过不及时而已。

Q: 为什么 while (b.isFlag()) 和while(a.getObjectB.isFlag()) 结果有区别,后者就可以拿到最新的flag值?

A: 一个volatile引用的域或者元素并不具备volatile特性,因为对于该域的写入并不会触发StoreLoad屏障,就不会强迫该域值立刻回写主存。不过其读特性并没有问题,对volatile的读操作一定是去主存当中读取的

所以a.getObjectB 在这里a就已经去从主存中读取了.

这一点因此也就解释了第一个例子中,我们修改a.flag,可以正常地读到flag值.

Q: 但是问题又来了,写入不能保证刷新到主存的话,岂不是即使while(a.getObjectB.isFlag()) 也是仍然很有可能失败的?经过刚才例子的反复测试,依然很难碰到终止线程失败的情况.

A: 这个例子还是无法测出这种刷新主存不及时的情况,毕竟即使是不及时刷新,最终刷新了还是可以让子线程结束的.

另外一篇博客 从汇编语句的角度分析了volatile的数组只针对数组的引用具有volatile的语义,而不是它的元素 , 暂时只能记录下来,待以后再深入原理去理解.

留下更多的疑惑

  1. 如上面所说,一个volatile引用的域或者元素并不具备volatile特性,因为对于该域的写入并不会触发StoreLoad屏障,就不会强迫该域值立刻回写主存。 如何证明?

  2. spring里用置换的方式真的不会出问题么? 多个线程同时读取了一个list,然后各自加一个元素进去,刷新,这样不就出了问题?

  3. 如果说这样并不安全,那么concurrent包里是怎么实现安全的list的呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    Object[] elements = getArray();
    int len = elements.length;
    Object[] newElements = Arrays.copyOf(elements, len + 1);
    newElements[len] = e;
    setArray(newElements);
    return true;
    } finally {
    lock.unlock();
    }
    }

    ReentrantLock 貌似是安全了, 但是为什么这里也用了置换数组啊???

评论和共享

SimpleAliasRegistry

发布在 Spring笔记

功能:注册/存放别名

唯一成员变量为

1
private final Map<String, String> aliasMap = new ConcurrentHashMap(16);

这Map 其实key是alias,value是name. 刚开始没注意可能搞反了呢

注册别名registerAlias()


举例来说 我们要注册的beanName为myTestBean , 别名为mtb

  1. 校验name和alias都不为空

  2. 如果name和alias一样,那map里删了这条name,结束

  3. 依据alias 即 mtb去map里面取已注册的name,如果真的有已注册过的话:

    1. registeredNamename相等的话,那就不用管,结束.
    2. 如果他俩不相等,这个工厂又不允许重写alias,那就抛异常! (allowAliasOverriding() 这个方法在SimpleAliasRegistry的子类里面有的会被重写.
  4. 如果前面都通过了,this.checkForAliasCircle(name, alias); 再次循环检查一遍,判断是否hasAlias(),如果是true的话,就报错,不是的话,走第5步. 具体hasAlias()流程如下:

    1. 循环找出registeredNamemyTestBean的那组键值对 (这里和上面的遍历不一样哦,这里是根据直接找重复的beanName的,上面是找重复的alias的
    2. 如果这个键值对的key即alias也是等于mtb的话,报错 (但是实际我们这第三步也查过这种情况)
    3. 如果这个myTestBean找出来的alias是”myTB”,那就还要走一遍hasAlias("myTB","mtb") ,换句话说,就是要看一看是不是有哪个bean名字叫myTB,别名叫mtb的,如果真的有,就返回true

    第四步的hasAlias() 这种判断是啥意思呢?

    想注册 mtb–myTestBean (别名–本名)

    如果已存在 mtb–myTestBean ,那么返回true

    如果已存在 myTB–myTestBean,

    ​ 且存在 mtb–myTB ,

    那就构成了 mtb–myTB–myTestBean 也返回true

    如果已存在 myTB–myTestBean,

    ​ 且存在 mta–myTB

    ​ 且存在 mtb–mta ,

    那就构成了 mtb–mta–myTB–myTestBean 还是返回true

    .

    这下看明白了吧,

    说明hasAlias()方法不止判断mtb是否已经做了myTestBean的别名,

    还判断了mtb是不是myTestBean的别名 的别名 的别名 的别名 的别名….最终能通过一条线导向myTestBean

  5. 如果前面各种校验全部通过 ,this.aliasMap.put(alias, name); 很简单,map里面塞值吧!

前面校验看着很啰嗦,其实总结起来都很简单

  1. 键值都不能为空
  2. 别键值相等,我会删了这个别名的(虽然你应该本来就添加不进来)
  3. 别名已用过,如果还是原name,那不用操作
  4. 别名已用过,这次换了新name,那就看你注册器允不允许我搞覆盖咯
  5. 别告诉我这个alias居然还是那个name的别名的别名…

第5种的情况让我突然想到,这种连环关系应该连申请都申请不了吧,即使真的想搞出连环关系,也应该是下面这种情况吧??

myTB–myTestBean已注册好了,新来个bean想注册beanName叫myTB

应该在申请注册beanName叫myTB的时候就校验过了吧!!!

好,翻回去找找注册beanName的时候,是不是已经校验过不能和已注册的alias重名!

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
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
String id = ele.getAttribute("id");
String nameAttr = ele.getAttribute("name");
//都忘掉了bean原本没有alias属性了......
//aliases数组是原本的name里面的多个值
//除非之前没id,有name, 那么从aliases里remove(0)出去做beanName,剩下的继续做alias
List<String> aliases = new ArrayList();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; ");
aliases.addAll(Arrays.asList(nameArr));
}

String beanName = id;
if (!StringUtils.hasText(id) && !aliases.isEmpty()) {
beanName = (String)aliases.remove(0);
if (this.logger.isDebugEnabled()) {
this.logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
//在这里校验一波beanName和alias的唯一性
this.checkNameUniqueness(beanName, aliases, ele);
}
....
}

//校验重名的方法
protected void checkNameUniqueness(String beanName, List<String> aliases, Element beanElement) {
String foundName = null;
//看已用名里是否包括了beanName
if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {
foundName = beanName;
}
//看已用名里是否用过了这些alias
if (foundName == null) {
foundName = (String)CollectionUtils.findFirstMatch(this.usedNames, aliases);
}

if (foundName != null) {
this.error("Bean name '" + foundName + "' is already used in this <beans> element", beanElement);
}

//注意这个地方, beanName和aliases都是全部加入usedNames的
//那就说明,在前面校验的时候,已用名就是包括已用beanName和已用alias的
//所以如果myTB--myTestBean已注册好了,myTB和myTestBean都被usedName收录了
//再想拿myTB做beanName肯定不行了
this.usedNames.add(beanName);
this.usedNames.addAll(aliases);
}

所以其实是校验过的啊,感觉上面考虑的第5种情况有点多此一举呢…

还是来看看类里面的其它方法吧

取消注册别名removeAlias()


很简单,map直接remove这个别名,如果remove结果是null,那抛个异常

判断是否为别名 isAlias()


1
return this.aliasMap.containsKey(name);

获取某个name的所有别名 getAliases(String name)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//加了个同步,
synchronized(this.aliasMap) {
this.retrieveAliases(name, result);
}
private void retrieveAliases(String name, List<String> result) {
Iterator var3 = this.aliasMap.entrySet().iterator();
//遍历map,找出value等于这个name的key
while(var3.hasNext()) {
Entry<String, String> entry = (Entry)var3.next();
String registeredName = (String)entry.getValue();
if (registeredName.equals(name)) {
String alias = (String)entry.getKey();
result.add(alias);
//注意这里,厉害了,居然还真的递归地去找它的别名的别名(的别名的别名...)
//到底什么地方能制造出链式的别名啊啊啊啊????
this.retrieveAliases(alias, result);
}
}
}

另外注意到,getAliases()方法在AbstractBeanFactory其实是有重写的啊,稍后去看看!

找本名canonicalName()


1
2
3
4
5
6
7
8
9
10
11
public String canonicalName(String name) {
String canonicalName = name;
String resolvedName;
do {
resolvedName = (String)this.aliasMap.get(canonicalName);
if (resolvedName != null) {
canonicalName = resolvedName;
}
} while(resolvedName != null);
return canonicalName;
}

方法很简单,就是一层一层地去找这个名字的本名.

AbstractBeanFactorygetAliases() 的重写


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
public String[] getAliases(String name) {
//找到这个name的真正的beanName,
//transformedBeanName里面的操作主要是去除头部所有&符号,以及调用上面的canonicalName()来找本名
String beanName = this.transformedBeanName(name);
List<String> aliases = new ArrayList();

boolean factoryPrefix = name.startsWith("&");
String fullBeanName = beanName;
if (factoryPrefix) {
//如果原name以&开头(可能多个&),表示工厂前缀
//bean全名就加上一个&
fullBeanName = "&" + beanName;
}

if (!fullBeanName.equals(name)) {
//如果bean全名还不等于原名, 说明原名可能是 &&&myTestBean
//那么把&myTestBean也作为它的一个别名
aliases.add(fullBeanName);
}
//调用上面我们SimpleAliasRegistry的方法
String[] retrievedAliases = super.getAliases(beanName);
String[] var7 = retrievedAliases;
int var8 = retrievedAliases.length;

for(int var9 = 0; var9 < var8; ++var9) {
String retrievedAlias = var7[var9];
//如果是工厂bean,配上工厂前缀&
String alias = (factoryPrefix ? "&" : "") + retrievedAlias;
if (!alias.equals(name)) {
//只要不等于我们的name,通通可以做为别名
aliases.add(alias);
}
}
//如果这个beanName对应的bean不是单例,并且这个beanName还没注册bean
if (!this.containsSingleton(beanName) && !this.containsBeanDefinition(beanName)) {
BeanFactory parentBeanFactory = this.getParentBeanFactory();
if (parentBeanFactory != null) {
//并且它还有父工厂,那就从它的父工厂里面找&myTestBean的别名 ,都算进来是它的别名
aliases.addAll(Arrays.asList(parentBeanFactory.getAliases(fullBeanName)));
}
}

return StringUtils.toStringArray(aliases);
}

虽然这个方法看得还不是很明白,但大概知道,这里的重写主要是牵扯到了这个bean的工厂相关属性,待后面了解了工厂应该就能清楚这里的别名与工厂的关系了.

解析别名resolveAliases(StringValueResolver valueResolver)


这里的入参需要传个值解析器,

加锁,复制出map,遍历map,拿到alias,registeredName, 以及,用解析器解析后的resolvedAlias,resolvedName

如果解析后的别名,本名有一者为空,或者二者相等,那么就要从map里移除这个alias,说明这个alias多余了,没作用.

否则

  1. 如果解析后别名和解析前别名一致,那么把解析前别名 alias指向解析后的本名,resolvedName (这里就直接覆盖掉原alias的映射了

  2. 如果解析后别名和解析前不一致

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //先找找解析后别名是不是在map里有映射
    String existingName = (String)this.aliasMap.get(resolvedAlias);
    if (existingName != null) {
    //如果有的话,并且还指向了其它值,那就要抛异常了
    if (!existingName.equals(resolvedName)) {
    throw new IllegalStateException("Cannot register resolved alias '" + resolvedAlias + "' (original: '" + alias + "') for name '" + resolvedName + "': It is already registered for name '" + registeredName + "'.");
    }
    //否则直接从map里移除原来的alias别名就够了
    this.aliasMap.remove(alias);
    break;
    }
    //注册解析后的别名--解析后的本名
    this.checkForAliasCircle(resolvedName, resolvedAlias);
    //注意到这里和普通注册唯一的区别就是这里还要移除原alias
    this.aliasMap.remove(alias);
    this.aliasMap.put(resolvedAlias, resolvedName);

所以总结一下这个处理解析Aliases的目的

遍历map,每个键值对都解析一遍,

如果这对能解析出新的一对合法别名–本名键值对,那么就把之前的那对删掉,注册新的这个解析后的一对,

此外检验下解析后的别名是不是被占用,如果占用了还指向错了值,就抛异常,

如果占用了也是指向解析后名字,那就说明解析后的这对已经注册好了,只需删掉旧的那对

评论和共享

本文要看啥


这个bean总算是解析完了,也装饰完了,等于是说信息我们都提取好了,现在该做的就是去注册啦.

1
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());

就是这个方法了

开始进入方法吧


1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
String[] var4 = aliases;
int var5 = aliases.length;

for(int var6 = 0; var6 < var5; ++var6) {
String alias = var4[var6];
registry.registerAlias(beanName, alias);
}
}
}
  1. 拿到beanName

  2. 用beanName和bd去registry里注册.

    registry是个接口,具体的实现在4.3.7版本中看到两种,一种是SimpleBeanDefinitionRegistry里,和它名字一样非常简单,就是塞进map, 另外一种就复杂了,稍后我们再来看

  3. 用beanName和alias去registry里注册

    注册aliases也是接口 ,BeanDefinitionRegistry还是继承自AliasRegistry的, 4.3.7只看到SimpleAliasRegistry一种实现

    注册alias就简单多了, SimpleAliasRegistry里有

    1
    private final Map<String, String> aliasMap = new ConcurrentHashMap(16);

    稍后我再另开一文把SimpleAliasRegistry来读一遍

注册BeanDefinitionHolder


除了之前的SimpleBeanDefinitionRegistry 直接往map里面塞值的注册方式之外,我们还有DefaultListableBeanFactory 这个类来实现注册功能,我们主要来了解一下这种注册方式

  1. 校验空参数

  2. 如果bdh是AbstractBeanDefinition ,那就要执行它的validate() 校验

    1. bdh的methodOverride和它的工厂不能同时存在
    2. 如果它beanClass有值,那就要为方法覆盖做准备 prepareMethodOverrides() ,遍历它的overrides,找这个Bean的Class里面有几个叫这个名字的方法, 如果0个,就抛错,如果1个,就设置重载属性为false表示没重载.
  3. 从这个工厂里面beanDefinitionMap 拿原来的叫这个beanName的bd出来,如果不为空,进行下列校验及操作

    书中源码spring 3.2 采用sychronized , 而4.3版本的beanDefinitionMap已经用上了ConcurrentHashMap,并且map的value用了BD,而不是3.2里面的Object

    1. 如果工厂不允许bean覆盖,那么抛错
    2. 如果旧bean的role值小于新的,或者新旧bd完全相等,或者不相等,各打印一下日志
    3. 把新的bd放入beanDefinitionMap
  4. 第一次注册这个bean, 如果本工厂的alreadyCreated 也是空的话,那就不用加锁,直接

    1
    2
    3
    4
    5
    6
     //map里塞入bd				
    this.beanDefinitionMap.put(beanName, beanDefinition);
    //已注册名字里加入beanName
    this.beanDefinitionNames.add(beanName);
    //唯一名册里移出beanName(啥时候加入了???)
    this.manualSingletonNames.remove(beanName);

    如果本工程已经有创建过bean了,那么接下来一段代码就要加synchronized了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Map var4 = this.beanDefinitionMap;
    synchronized(this.beanDefinitionMap) {
    //map里塞入bd
    this.beanDefinitionMap.put(beanName, beanDefinition);
    //注意到后面这段操作其实是新建个数组,加上这次注册beanName,然后替换之前的数组....
    //为什么这么复杂呢???
    List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
    updatedDefinitions.addAll(this.beanDefinitionNames);
    updatedDefinitions.add(beanName);
    this.beanDefinitionNames = updatedDefinitions;
    //如果唯一名册里包含beanName
    if (this.manualSingletonNames.contains(beanName)) {
    //就移出去,但是这里也和上面一样,搞替换....
    Set<String> updatedSingletons = new LinkedHashSet(this.manualSingletonNames);
    updatedSingletons.remove(beanName);
    this.manualSingletonNames = updatedSingletons;
    }
    }

    疑问: 这里都用了替换,明明beanDefinitionNamesmanualSingletonNames 都是volatile声明的,每次读都从主内存取,每次写都都将值写到主内存, 这里为什么还要这么麻烦得去开新的list和set,替换旧的呢?

    答:从网上搜到,volatile修饰的变量如果是对象或数组之类的,其含义是对象或数组的地址具有可见性,但是数组或对象内部的成员改变不具备可见性,那等于就是说非得整个地址替换才行咯.

    很爽,从这本书要跳到我正在读的并发编程相关的内容了,这几天再去研究一波volatile,可以再开一文. 准备参考地址

然后this.frozenBeanDefinitionNames = null; 这个冻结/取消冻结bdNames的功能暂时也不知道干啥的,在注册和删除bd的地方看到了这样的置为null,

​ 只在freezeConfiguration() 方法内看到把它置为bdNames转的String[] .

​ 另外在getBeanDefinitionNames() 方法里有判断这个

然后

1
2
3
4
5
//如果有老bd或者 这个bean是单例对象?
if (oldBeanDefinition != null || this.containsSingleton(beanName)) {
//重置bean
this.resetBeanDefinition(beanName);
}

重置的主要内容有

  • 清除mergedBD
  • 销毁单例
  • 遍历之前的bean,看新注册的这个bean是谁的爹(parent),那些儿子也都要调用这个方法进行重置

不得不说这个DefaultListableBeanFactory 的成员变量实在太多了,搞不清楚一个个都是要干啥的,后面再次遇到的时候再慢慢看吧

注册Alias


4.3.7版本的实现在SimpleAliasRegistry 里, 详情见 这一篇博文

注册完成


其实没有操作了, 但是作者留了个位置在这

1
this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));

如果程序开发人员需要对注册BeanDefinition时间进行监听时,可以通过注册监听器的方式来做.Spring并没有再此做任何逻辑处理

告一段落


至此,我们的解析注册Bean标签可算是告一段落啦,虽然里面还留下了很多疑问待后续阅读中弄清.

先想想之前是在哪分叉出去的吧.

1
2
3
4
5
6
7
8
9
10
11
12
13
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
//就是这里啦,我们先去阅读最复杂的bean的
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
//这个地方我还记得,beans标签是绕回去解析的,不用看了
this.doRegisterBeanDefinitions(ele);
}
}

再之前呢,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();

for(int i = 0; i < nl.getLength(); ++i) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element)node;
if (delegate.isDefaultNamespace(ele)) {
//就是这个地方,差不多想起来了,beans标签里面的子元素, 如果是默认命名空间,就按默认方式去解析
this.parseDefaultElement(ele, delegate);
} else {
//这里是个分叉,解析自定义元素,以后再看
delegate.parseCustomElement(ele);
}
}
}
} else {
//这和上面一样的分叉,解析自定义元素
delegate.parseCustomElement(root);
}

}

再之前呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute("profile");
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}

return;
}
}
}

this.preProcessXml(root);
//从这里走出去的
this.parseBeanDefinitions(root, this.delegate);
this.postProcessXml(root);
this.delegate = parent;
}

到这还要温习一遍的话,可以去看Spring的第一篇笔记

哈哈,这么点东西,读了一个礼拜,读了后面的忘了前面的,还是在读读记记的情况下,

要是没有这个笔记,这时候估计已经迷路得要放弃了,不容易啊

不说了 SimpleAliasRegistryvolatile 两篇文章的坑还留着呢,继续…

评论和共享

本文要看啥


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException var5) {
this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
}

this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}

}

前面的笔记中,我们已经走完了这个方法中的第一步,感觉第一步就走了很远,眼瞅要迷路了,但无论如何,总归是拿到了我们的bdHolder, 里面已经存放了Bean标签里面的默认的元素.

如果bdHolder不为空的话,我们可以继续往下走啦.

今天来研究下这段代码

1
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

看方法名字就大概知道,这里要做的事情就是 如果有需要的话,装饰我们刚拿到的bdHolder.

那么何为需要呢?下面这样的场景便是了

1
2
3
<bean id="test" class="test.myClass">
<mybean:user username="aaa"/>
</bean>

这里的自定义标签和

1
2
3
<mvc:resources mapping="/img/**" location="img/"/>
<aop:config/>
<tx:advice id="transactionAdvice" transaction-manager="transactionManager"/>

并不同,这里的是在bean内的, 而mvc那种标签是在beans里面,与bean同级的

开始进入方法吧


这次要看的主要方法就是 decorateBeanDefinitionIfRequired

这个方法主要是拆开我们之前的bean,将其各属性和各子节点全部变为node,

然后带着每个node和之前的bd去 decorateIfRequired

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  public BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {
String namespaceUri = this.getNamespaceURI(node);
//看看是不是默认命名空间, 如果是的话那就不用管了, 在这方法里,我们只需处理自定义的标签
if (!this.isDefaultNamespace(namespaceUri)) {
//拿到这个命名空间的解析器 handler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
//如果能找到解析器,我们就可以进入装饰方法啦
if (handler != null) {
return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
}
//以下都是没找到解析器的
//命名空间不为null,而且还以spring家的网址开头,那就报错表示找不到spring解析器
if (namespaceUri != null && namespaceUri.startsWith("http://www.springframework.org/")) {
this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
} else if (this.logger.isDebugEnabled()) {
//如果能打日志的话,打个日志完事
this.logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
}
}

return originalDef;
}

具体的用handler解析node会在后面详细学习,这个就先到这吧!

前面几篇文章都长得没法看了…吸取教训 篇幅尽量不要太长了.

评论和共享

作者的图片

heeexy

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


JAVA


南京