本文要看啥

之前我们看解析默认标签,从最核心的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 方法

评论和共享

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);

评论和共享

以通过BeanFactory获取bean为例学习,实际项目中更多是使用ApplicationContext

img

1.读取配置文件 beanFactoryTest.xml

2.通过文件资源获取工厂

3.从工厂中拿bean

读配置

配置文件封装

ClassPathResource类 继承–AbstractFileResolvingResource

​ 继承–AbstractResource

​ 实现–Resource

​ 继承–InputStreamSource接口 唯一方法: getInputStream()

Resource接口提供了一系列方法来封装底层资源,比如 exists() isReadable() getURL() getFile() getDescription() 等等

XmlBeanFactory的实例化

大概步骤

1
2
3
4
5
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);//1.设置父工厂(null)...本例未使用到此
this.reader = new XmlBeanDefinitionReader(this);//2.实例化XmlBeanDefinitionReader, 注册到此工厂
this.reader.loadBeanDefinitions(resource);//3.重要部分,通过resource来加载beanDefinations,bean的一些定义
}

loadBeanDefinitions(resource)方法就是下面的主要要看的

它做了几件事:

  1. 将resource用EncodedResource包装一下,编码要用, 本例没特定的charset和encoding因此都是null
  2. 获取当前正在加载的资源集 Set currentResources ,如果currentResources 是null 就初始化个 new HashSet(4), 然后将我们要读的encodedResource加到这个set里面去

如果返回false说明正在加载这个xml了,报个错,不继续往下读了

当读完这个资源后,在finally里面会从set里移出这个encodedResource,如果set空了,还会remove这个currentResources,

这个set是放ThreadLocal里面的,

private final ThreadLocal> resourcesCurrentlyBeingLoaded;

是线程安全的,功能是防止同时循环加载同一个xml文件

  1. 从encodedResouce里取出文件的inputStream 拿去初始化个InputSource(有enconding的话,给inputSource也设个编码,本例为null)然后 inputSource和encodedResouce里面的resouce一起拿去执行doLoadBeanDefinitions(..)方法,稍后重点讲
1
var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
  1. 关闭资源, 释放currentResources 等

关键:doLoadBeanDefinitions

将资源转为Document

1
Document doc = this.doLoadDocument(inputSource, resource);
  1. 获取xml的验证模式 , 里面的核心是自动检测,自动检测的核心是拿着inputStream读,判断是否含有”DOCTYPE”,包含就是DTD,没有就是XSD
  2. 拿到EntityResolver , 这个主要是用来给xml验证时找DTD文件获取路径找得快的, 不用全去spring网站拿dtd,略
  3. 解析document, 创建DocumentBuilderFactory,通过此工厂创建DocumentBuilder,进而解析inputSource来返回document对象. 此为通过SAX(Simple API for XML)来解析的常见方法,略过

注意到本方法的参数中又用到resource, 明明inputSource就是从resource里拿inputStream生成出来的啊, 相当于inputSource就是儿子, doLoadDocument方法请了儿子又去请爹,烦不烦?

其实主要是之前设计的时候, 获取XML验证模式的自动检测方法,用的是Resource接口,isOpen()方法因此不得不用

isOpen()方法是Resource接口的方法,其实现返回false(比如AbstractResource),有的返回的是true(InputStreamResource).

注册bean

1
registerBeanDefinitions(doc, resource);

先走流程

1
2
3
4
5
6
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
int countBefore = this.getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
  1. 创建阅读器documentReader
  2. 记录加载BeanDefinition的个数,
  3. documentReader来加载注册doc和封装resource而生成的Context

继续走流程, 在reader内设置context,拿到root元素,重点在最后解析root

1
2
3
4
5
6
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
this.doRegisterBeanDefinitions(root);
}
开始核心部分 doRegisterBeanDefinitions
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
  protected void doRegisterBeanDefinitions(Element root) {
/*
基于原本的detegate,
加上这里的context和root(XML内容)修饰一遍,作为此处要用的解析代理ParserDelegate
方法结束时,会把原来的解析器置换回来
*/
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
//如果命名空间为空或者等于"http://www.springframework.org/schema/beans" 就校验里面的一些属性
if (this.delegate.isDefaultNamespace(root)) {
//如果有profile属性,就要去环境变量中去寻找
String profileSpec = root.getAttribute("profile");
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
//如果不是当前环境变量的属性值,就跳过这个xml文件
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);
//将delegate换回去
this.delegate = parent;
}

this.preProcessXml(root); this.postProcessXml(root);此处的预留 设计模式—模板方法模式

profile

关于profile属性书中讲用途讲的很清楚,从源码中我了解到

  1. profile可以同时指定多个属性,比如 只需当前环境变量里值满足其中一种就OK

  2. 多个属性可以支持多种分隔符 逗号 分号 空格 源码是

    1
    String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
  3. 可以用一个感叹号修饰属性 比如 表示”非” , 不可多个感叹号

  4. 读取并设置spring.profiles.active属性时还加了sychronized

  5. spring.profiles.active属性值可以多个 ,用逗号隔开

  6. 判断过程其实是

    1
    return currentActiveProfiles.contains(profile) || currentActiveProfiles.isEmpty() && this.doGetDefaultProfiles().contains(profile);

    spring.profiles.active里包含这个属性, 或者spring.profiles.active为空,但是spring.profiles.default里包含这个属性

ps.这个属性看明白很有成就感,书上没解释源码, 看到这段之前先是自己埋着头进去钻源码,莫名其妙卡了很久,并不知道整段的功能是啥,继续看书看到此处,了解了这个profile 的功能,再回头结合源码,就恍然大悟:)

判断是不是默认命名空间

1
2
3
4
5
6
7
public boolean isDefaultNamespace(String namespaceUri) {
return !StringUtils.hasLength(namespaceUri) || "http://www.springframework.org/schema/beans".equals(namespaceUri);
}

public boolean isDefaultNamespace(Node node) {
return this.isDefaultNamespace(this.getNamespaceURI(node));
}

node获取命名空间方法 getNamespaceURI()主要在 rt.jar里有很多种不同的实现,返回字符串,就不再去深究了

再深一层 parseBeanDefinitions

不贴源码了,这个方法主要是先判断这个节点是不是默认命名空间 ,不是就按自定义的方式解析,

是的话就遍历节点,解析每个节点.

每个节点解析之前都还会去判断一遍是否默认命名空间,

如果不是就按自定义方式解析,如果是就才按默认方式解析

默认标签如

1
<bean id="test" class="test.testBean">

自定义标签如

1
<tx:annotation-driven/>

好,书的第二章到此为止,第三章将继续深入默认标签的解析

评论和共享

作者的图片

heeexy

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


JAVA


南京