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会在后面详细学习,这个就先到这吧!

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

评论和共享

1
2
private final Map<Integer, ConstructorArgumentValues.ValueHolder> indexedArgumentValues = new LinkedHashMap(0);
private final List<ConstructorArgumentValues.ValueHolder> genericArgumentValues = new LinkedList();

这个保存器的核心就在他的LinkedHashMap和LinkedList

疑问: 这里为什么选用linked来储存呢?

答:

添加有index的关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  private void addOrMergeIndexedArgumentValue(Integer key, ConstructorArgumentValues.ValueHolder newValue) {
//拿当前这个位置的值
ConstructorArgumentValues.ValueHolder currentValue = (ConstructorArgumentValues.ValueHolder)this.indexedArgumentValues.get(key);
//如果当前位置有值,并且新值是接受合并的话,那合并一波之后作为新值
//如果不接受合并的话,那扔了之前的value不管了,只用新来的value
if (currentValue != null && newValue.getValue() instanceof Mergeable) {
Mergeable mergeable = (Mergeable)newValue.getValue();
if (mergeable.isMergeEnabled()) {
newValue.setValue(mergeable.merge(currentValue.getValue()));
}
}
//将新值放入LinkedHashMap
this.indexedArgumentValues.put(key, newValue);
}

疑问:在获取vh之前我们不就验证过index不能重复么?为什么这里还有可能搞一波合并?是有其它地方也调用这个方法,那里可以合并?

答: 注意到这个类里有个方法,addArgumentValues(ConstructorArgumentValues other),貌似就是专门和其它CAV搞合并的,用CAV做构造参数的那个构造器调用了此方法,这里会有可能需要合并

添加没有index的参数

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
public void addGenericArgumentValue(ConstructorArgumentValues.ValueHolder newValue) {
//校验不为空
Assert.notNull(newValue, "ValueHolder must not be null");
//如果list内已经含有此value则跳过
if (!this.genericArgumentValues.contains(newValue)) {
this.addOrMergeGenericArgumentValue(newValue);
}
}
//添加或合并VH
private void addOrMergeGenericArgumentValue(ConstructorArgumentValues.ValueHolder newValue) {
if (newValue.getName() != null) {
Iterator it = this.genericArgumentValues.iterator();
//如果新VH有name,那么就要遍历之前的list,找出同名的老VH
//看新VH接不接受合并,接收就合并下再删,不接受就直接删掉.
while(it.hasNext()) {
ConstructorArgumentValues.ValueHolder currentValue = (ConstructorArgumentValues.ValueHolder)it.next();
if (newValue.getName().equals(currentValue.getName())) {
if (newValue.getValue() instanceof Mergeable) {
Mergeable mergeable = (Mergeable)newValue.getValue();
if (mergeable.isMergeEnabled()) {
newValue.setValue(mergeable.merge(currentValue.getValue()));
}
}
it.remove();
}
}
}
//将新VH塞入list里面
this.genericArgumentValues.add(newValue);
}

发现addGenericArgumentValue(Object value) 和addGenericArgumentValue(Object value, String type)就没这么麻烦,因为他们拿Object构造的肯定没name,也不会为null,直接懒得考虑合并,也不去验证list是否已存在这个VH, 直接塞…有相同的覆盖掉就是了…

只是暂时还没看到在哪这么直接地调用这种塞Object的方法

既然都看完了CAV的赋值,干脆来看看它的取值和其它方法吧!

  1. 获取它的list,map都是 Collections.unmodifiableMap(this.indexedArgumentValues);

    Collections.unmodifiableList(this.genericArgumentValues);这样都是返回只读的集合出去,调用set/add/remove的话会报错

  2. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public int getArgumentCount() {
    return this.indexedArgumentValues.size() + this.genericArgumentValues.size();
    }

    public boolean isEmpty() {
    return this.indexedArgumentValues.isEmpty() && this.genericArgumentValues.isEmpty();
    }

    public void clear() {
    this.indexedArgumentValues.clear();
    this.genericArgumentValues.clear();
    }
  3. 重头戏,拿参数值,这里又分为3种情况,取Indexed的,取Generic的,拿个index就想直接取的,每种情况里都有不同参数的重载.

    我估计实际使用应该是都用第三种情况吧,因为包括了前两种,

    而且真正构造需要拿参数的时候,肯定是想给个index,按照顺序去CAV里直接拿,不管放入参数时是否indexed

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public ConstructorArgumentValues.ValueHolder getArgumentValue(int index, Class<?> requiredType, String requiredName, Set<ConstructorArgumentValues.ValueHolder> usedValueHolders) {
    Assert.isTrue(index >= 0, "Index must not be negative");
    //先根据index去Map里面找
    ConstructorArgumentValues.ValueHolder valueHolder = this.getIndexedArgumentValue(index, requiredType, requiredName);
    if (valueHolder == null) {
    //如果没有的话,就再去list里面找, 从这可以看出,我们在XML里写参数的时候,最好是带上index,在这里找的快一点哦!
    valueHolder = this.getGenericArgumentValue(requiredType, requiredName, usedValueHolders);
    }
    return valueHolder;
    }

从map里面取Indexed的

1
2
3
4
5
6
7
8
9
10
11
       public ConstructorArgumentValues.ValueHolder getIndexedArgumentValue(int index, Class<?> requiredType, String requiredName) {
Assert.isTrue(index >= 0, "Index must not be negative");
ConstructorArgumentValues.ValueHolder valueHolder = (ConstructorArgumentValues.ValueHolder)this.indexedArgumentValues.get(index);
//这里一波||&&!简直了...总结一下有哪些情况返回null吧
/**
* 1. 根据这个index取出来的VH就是null
* 2. VH的type不为null,可是( requiredType为null 或者 两者type不匹配)
* 3. VH的name不为null,requiredName不为"" 可是( requiredName为null 或者 两者name不匹配)
*/
return valueHolder == null || valueHolder.getType() != null && (requiredType == null || !ClassUtils.matchesTypeName(requiredType, valueHolder.getType())) || valueHolder.getName() != null && !"".equals(requiredName) && (requiredName == null || !requiredName.equals(valueHolder.getName())) ? null : valueHolder;
}

注意到一个细节,在判断name的时候, requiredName如果给了”” 这个值,就不会拿去判断name是否匹配…不知这是何用意…

从list里面取

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
public ConstructorArgumentValues.ValueHolder getGenericArgumentValue(Class<?> requiredType, String requiredName, Set<ConstructorArgumentValues.ValueHolder> usedValueHolders) {
Iterator var4 = this.genericArgumentValues.iterator();

ConstructorArgumentValues.ValueHolder valueHolder;
//这里的4层嵌套循环和疯狂的||&&!更是简直了........从里向外一层层看吧

do {//最后看这个最外层,如果同时满足下列条件,说明这个VH还不是我们要的
/**
* 1.requiredType不是null
* 2.VH的Type却是null
* 3.VH的name也是null
* 4.type校验通不过
* (第4步校验要通过需要满足以下情况之一
* a. value==null 且 requiredType不是基本数据类型
* b. value不等于null 且 (valueClass继承或实现requiredType
* 或valueClass包装或解包装后能等于requiredType..)
*/

while(true) {//再次 看这层,如果匹配到类型相同的,就说明有可能找到我们需要的参数啦,跳出来到最外层再接受一次校验.
//如果不匹配的话,继续执行里面的循环找下一个VH吧.

do {//其次看这层循环,如果VH有name,requiredName不为"",但rqName为null或者两者不相等,跳过此VH,这说明VH有name的话就很关键,必须相同啊,除非rqName==""

do {//首先看这里的循环, 取下一个VH,如果取完了,就返回null
if (!var4.hasNext()) {
return null;
}
valueHolder = (ConstructorArgumentValues.ValueHolder)var4.next();
//如果set不为空,并且set里已经包含这个VH了就跳过此VH
} while(usedValueHolders != null && usedValueHolders.contains(valueHolder));
} while(valueHolder.getName() != null && !"".equals(requiredName) && (requiredName == null || !valueHolder.getName().equals(requiredName)));

if (valueHolder.getType() == null || requiredType != null && ClassUtils.matchesTypeName(requiredType, valueHolder.getType())) {
break;
}
}
} while(requiredType != null && valueHolder.getType() == null && valueHolder.getName() == null && !ClassUtils.isAssignableValue(requiredType, valueHolder.getValue()));

return valueHolder;
}

总而言之,从list里面找到我们需要的那个参数真是非常麻烦,需要这个VH满足下列条件:

  1. 不在usedVH的那个set里
  2. 有name且rqName不是”” 就必须name匹配
  3. 有rqType的话,说明必须来满足这个type的匹配,那么type怎么确认是匹配的呢?
    1. type不是null,那就直接去匹配
    2. type是null,如果name也是null,(说明之前用不写name方式绕过了上面的第2步name验证,如果上面第二步用的是””方式就不用管了,直接算通过), 那就必须要value.getClass能去真正的和rqType进行匹配 (继承,实现,包装,解包各种方式能匹配上就OK)

这里是真的麻烦,再换个角度再梳理一遍!

  1. 如果rqName写了比如”age”,VH里name也写了的话,那就必须name也是”age”才通过
  2. 如果rqName写个””,那就表示name属性完全不管了,都算通过
  3. 如果rqName是null,那有name的VH都不行, 没name的VH可以通过
  4. 如果rqType没写,那就表示type不管了,都算通过(估计实际调用不会name和type全部不管吧…….)
  5. 如果rqType写了,那就表示要验证type啦,从上面放行过来的VH需要校验了
    1. 如果VHtype为null,但是有name,说明通过1.2放过来的,都算ok (那个””就很坑了,VH随便填个name,不填type就可能全部通过了)
    2. 如果VHtype为null,name也是null,那就去ClassUtils.isAssignableValue校验VHvalue的本质class能否对得上.
    3. 如果VHtype不是null,那就必须type也完全一致

我预测正常调用情况应该是rqName和rqType都有明确要求吧,那么正常流程就应该是

  1. VH有name最好,匹配上直接通过!
  2. VH如果没name,有type,那就去匹配type!
  3. VH如果啥都没有,那就拿VHvalue的class去跟rqType匹配,这总跑不了了吧!

这个地方盯着看了几个小时,不知道作者写这么复杂是何居心,调用的时候也准备做这么复杂么?拿构造器参数的时候就不能把requiredName和requiredType都带过来??? 还搞些双引号””这种怪事情???

评论和共享

1.解析BeanDefinition

1
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

作用就是将element封装进bdHolder里面.

其源码很长,不贴了,但功能流程还很清晰

  1. 处理id,name,aliases. 关键是需要beanName ,

    name分隔开做aliases的list.

    beanName优先用id,没id的话就从aliases里remove(0)出来一个,

    如果有beanName了,校验beanName和aliases唯一性

​ 如果还是没beanName,等会再给它用方法生成个

  1. 将element解析,放入GenericBeanDefinition里面
  2. 把上一步拿到的bd连带着aliases转String[], beanName一起封装一下成为BeanDefinitionHolder返回;如果上一步返回null,就直接返回null

这里的关键步骤显然是第二步,element转beanDefinition

1
AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);

这个方法也很长,大概步骤是

  1. this.parseState.push(new BeanEntry(beanName)) 开头push,结尾pop,不明白是做什么???
  2. 获取下className和parent
  3. 拿着className和parent去创建个GenericBeanDefinition bd
  4. 拿着bd和element进行一系列的解析步骤,把各种值塞入bd
  5. 返回bd
  6. this.parseState.pop();

解析各种属性

1
this.parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);

这个方法的代码丧心病狂地达到了70行,相比于目前读到的其它源码,确实是最长的一个.

但是里面的内容其实并不复杂,就是一个个地从element拿属性,塞属性进bd, 各种 get & set .感觉确实不太好重构.

解析了很多属性,包括scope,abstract,lazy-init,autowire…

解析子元素meta

解析子元素lookup-method

解析方法和meta差别很小,这里主要是去了解下lookup-method的功能

1
2
3
<bean class="beanClass">
<lookup-method name="method" bean="non-singleton-bean"/>
</bean>

method是beanClass中的一个方法,beanClass和method是不是抽象都无所谓,不会影响CGLIB的动态代理,根据项目实际需求去定义。non-singleton-bean指的是lookup-method中bean属性指向的必须是一个非单例模式的bean,当然如果不是也不会报错,只是每次得到的都是相同引用的bean(同一个实例),这样用lookup-method就没有意义了。

参考: Spring - lookup-method方式实现依赖注入

解析子元素replaced-method

这个不仅可以动态地替换返回实体bean,而且还能动态地更改原有方法的逻辑!!!

解析子元素constructor-arg

这里提取构造参数的一些属性值就相比之前复杂多了.

  1. 提取index,type,name,判断是否有index属性值
  2. 如果有index:
    1. 构造Entry压入parseState栈
    2. 解析constructor-arg的子元素
    3. 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素
    4. 将index,type,name属性一并封装在ValueHolder中,
    5. 验证index是否用过了,用过则抛错,跳过此条参数
    6. ValueHolder添加至当前BeanDefinition的construArgumentValues的indexedArgurmentValues属性中.
    7. 弹出栈
  3. 如果没有index:
    1. 同上压入栈
    2. 解析constructor-arg的子元素
    3. 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素
    4. 将index,type,name属性一并封装在ValueHolder中,并且添加至当前BeanDefinition的construArgumentValues的genericArgurmentValues属性中.
    5. 弹出栈

可以看出,有没有index其实流程没区别,主要在于在bd中保存的位置不同,另外有index的需要验证下index的唯一

parsePropertyValue

解析constructor-arg的子元素

1
Object value = this.parsePropertyValue(ele, bd, (String)null);

这个方法也很长,里面的关键在于此处解析constructor-arg下面的子元素时,下面三种情况必须只能占一种

  • 有ref属性
  • 有value属性
  • 有子元素(description和meta除外,这两种不用处理),子元素只能列一个

上面三种情况对应不同的处理方法:

  1. ref:

    校验下ref的值不能为空,然后

    1
    2
    3
    RuntimeBeanReference ref = new RuntimeBeanReference(refName);
    ref.setSource(this.extractSource(ele));
    return ref;

    用RuntimeBeanReference封装下这个要引用的bean的beanName,塞下resource,返回

    查看BeanReference源码时注意到有RuntimeBeanReference和RuntimeBeanNameReference两种实现,貌似区别不大,RuntimeBeanReference多个toParent的boolean属性,此处给的也是false值.

  2. value:

    1
    2
    3
    TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute("value"));
    valueHolder.setSource(this.extractSource(ele));
    return valueHolder;

    用TypedStringValue封装下value的值 返回

    TypedStringValue和BeanReference都是实现BeanMetadataElement的,实现了Object getSource();方法

  3. 子元素parsePropertySubElement: 这个又复杂了,判断10+种情况

    • 如果不是默认命名空间,则按其自定义的方式去解析

    • bean元素,按照本文的方式解析,拿到一个BeanDefinitionHolder bdHolder,bdHolder不为空的话,可能还要装饰一下(在这个元素的属性或子元素节点命名空间不为默认情况下, 用自定义方式解析后去装饰),返回最终解析+装饰的结果 bdHolder

    • ref: 主要还是拿要引用的beanName, 取值顺序为其属性 bean–local–parent,和之前的ref一样,用RuntimeBeanReference封装下这个要引用的bean的beanName,塞下resource,返回

    • idref:和ref差不多,取值顺序为bean–local

    • value: 拿文本值,拿type值,没type就用传来的默认typeName,此处为null,然后根据value,typeName和BeanClassLoader来构造一个TypedStringValue,返回

    • null:用null来new一个TypedStringValue,返回

    • array:

      1. 拿value-type和子节点list

      2. 用type和子节点size构建个ManagedArray

        ManagedArray继承ManagedList 继承ArrayList ,

        ManagedList实现Mergeable和BeanMetadataElement ,可以有merge和getSource

        然后设置source和merge属性 (此处还多余重复了一遍setElementTypeName(elementType);),

      3. 然后 this.parseCollectionElements(nl, target, bd, defaultElementType); 对其子节点进行一个个的再解析子元素,还是调用parsePropertySubElement方法,只是带了个默认类型defaultElementType

        看到这已经有点晕了, 最初的那个bd都跑了多少路了?? 子元素里的属性也往bd里直接塞,不就丢失父子结构了么???

    • list: 和array没什么差别,仅有的区别在于用ManagedList ,(ManagedArray还是继承自他的)

    • set:同上,仅有的区别在于用ManagedSet,继承自LinkedHashSet

    • map:这个方法100行,非常长

      1
      2
      3
      4
      5
      6
      <constructor-arg name="score">
      <map>
      <entry key="math" value="90"/>
      <entry key="english" value="85"/>
      </map>
      </constructor-arg>
      1. 拿key-tpye和value-type,子节点entry的list

      2. 构建ManagedMap,设source,KeyTypeName,ValueTypeName,MergeEnable

      3. 遍历entry节点

      4. 拿entry再下一层的子节点,遍历,如果是key节点,则设这个entry的keyEle为此,key只能最多出现一遍, 然后剩下的节点除了description之外最多只能有一个,作为valueEle.所以此处表明这里貌似可以这么写???

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <constructor-arg name="score">
        <map>
        <entry >
        <key>哈哈</key>
        <vvvaaluue>这样写value??</vvvaaluue>
        <description>描述随便写,相当于注释</description>
        <description>描述随便写,相当于注释</description>
        </entry>
        </map>
        </constructor-arg>

        然后又和解析constructor-arg的子元素类似的情况,三种情况必须只能出现一种:1.”key”属性;2.”key-ref”属性;3.”key”子元素, 三种对应不同的解析

        key:TypedStringValue来封装

        key-ref:RuntimeBeanReference来封装

        key子元素: 里面必须且只能有一个节点,进行parsePropertySubElement,这和在array那看到的一样了

        三种解析后返回的Objext赋值给key

        然后解析value,和上面解析key类似,以下三种必须只能出现一种:

        (有点区别在于value-ref属性和value-type属性不能并存,

        有value-type时就必须要有value属性或者value子节点)

        value属性:拿value-type,没有就用默认的value-type(map节点下的,当然也可能没有),构建TypedStringValue

        value-ref属性:构建RuntimeBeanReference

        value子节点:老样子,还是parsePropertySubElement这样来继续解析子节点

        key,value都拿到之后,会塞入ManagedMap,最终返回ManagedMap

    • props:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <constructor-arg>
      <props>
      <prop key="firstName">
      Rob
      </prop>
      <prop key="secondName">
      Harrop
      </prop>
      </props>
      </constructor-arg>

      这个就简单了,无非就是遍历props下面的prop元素,拿key属性,拿文本值的trim(),塞入Properties对象,返回这个properties

至此,解析constructor-arg的子元素算是完成了, 都快忘掉这只是解析constructor-arg里的第一步了…

总之这一步解析完子元素,返回Object对象回来,作为value

ValueHolder

用上一步返回回来的value,构造个ValueHolder

ValueHolder类在ConstructorArgumentValues类里,实现了BeanMetadataElement,属性不多,就value,type,name,source,converted,convertedValue

然后constructor-arg有type属性就给vh塞type,有name塞name, 塞source

校验完index唯一之后

1
bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);

构造器参数塞入bd的constructorArgumentValues,后面无index的处理方式和这里有index的主要区别就是在往这个constructorArgumentValues里面塞的位置不同.

BeanDefinition里定义了

1
ConstructorArgumentValues getConstructorArgumentValues();

这个接口,在AbstractBeanDefinition里有

1
private ConstructorArgumentValues constructorArgumentValues;

目测专门存放构造器的参数值的

下面我们来熟悉一下ConstructorArgumentValues这个类吧..

(这个类实在太麻烦了,写着写着2000多字,重开一文了,即下一篇笔记)

解析子元素property

  1. 拿name属性并且校验
  2. parseState.push()
  3. 校验name在bd.getPropertyValues()里面唯一
  4. parsePropertyValue 提取出property里面的子元素或者ref或者value,这个步骤和之前的解析子元素constructor-arg里面的parsePropertyValue是同一个方法! 只是这里调用带上了propertyName,这里的name是不可能为空的 ,因为第一步校验了
  5. 拿第4步的结果val和propertyName封装一个PropertyValue ,这个也是继承BeanMetadataAttributeAccessor的,
  6. 然后和之前的解析子元素meta一模一样来解析这里面的meta子元素
  7. setSource
  8. propertyValue塞入bd
  9. parseState.pop()

解析子元素Qualifier

Qualifier通常都是以注解形式使用的,用于在注入bean时明确指明Bean的名称

  1. 拿type属性并校验

  2. parseState.push()

  3. 1
    AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
  4. setSource

  5. 拿value属性,如果有的话就塞入qualifier

  6. 遍历子节点,取其name和value(两者都必须存在),new BeanMetadataAttribute ,塞入qualifier

  7. qualifier塞入bd

  8. parseState.pop()

bd设好resource和source,作为结果成功返回.

2.生成beanName

如果这个bean之前没有id或name,那就为它生成个beanName

其规则也略烦,不过感觉不重要,简单带过一下,有一种生成规则如下

命名主要根据的属性有

getBeanClassName

父bean名+$child

工厂名+$created

‘class’ nor ‘parent’ nor ‘factory-bean’

后缀再加上#

如果是内部bean再就加bd生成的hexString

如果不是,就再加registry里这种bean的序号 从0开始

3.返回BeanDefinitionHolder

1
2
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);

评论和共享

作者的图片

heeexy

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


JAVA


南京