OOPcoder

提醒:本文是基于Spring 3.0.0.RELEASE 版本进行讲解的,其他版本可能稍有差异,在贴源码的时候,部分不影响流程的代码也在本文省略了

经过上面【Spring加载过程源码解析系列】的学习,我们完成了 Bean配置的解析和注册过程,容器中存在的是 Bean 对应的 BeanDefinition,Bean并没有完成实例化,接下来我们看看到底是怎么实例化的。

开门见山,Bean 的实例化是在使用前完成的,即在方法 getBean 中 实例化。

1. getBean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}

public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
return doGetBean(name, requiredType, null, false);
}

public Object getBean(String name, Object... args) throws BeansException {
return doGetBean(name, null, args, false);
}

public <T> T getBean(String name, Class<T> requiredType, Object... args) throws BeansException {
return doGetBean(name, requiredType, args, false);
}

四个重载方法都指向了 doGetBean 方法

2. doGetBean
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
private <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, 
boolean typeCheckOnly) throws BeansException {

// 将传入的name转换为实际的beanName, a -> a ; &a -> a;
// 如果该bean类型class是FactoryBean, 无论是getBean(a),还是getBean(&a),
// 都是先实例化FactoryBean, 然后再返回实际需要的bean
final String beanName = transformedBeanName(name);
Object bean;
// 先从缓存中获取单例
Object sharedInstance = getSingleton(beanName);
// 缓存中存在单例,并且 args 为空(因为只有prototype才能带参数 args),才进入 getObjectForBeanInstance
if (sharedInstance != null && args == null) {
// 获取实际需要的bean, sharedInstance 可能为 FactoryBean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 检查是否为 当前线程正在创建的prototype 实例
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 检查是否存在当前beanFactory中,不存在则去父工厂(存在的话)中找
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
String nameToLookup = originalBeanName(name);
if (args != null) {
return (T) parentBeanFactory.getBean(nameToLookup, args);
} else {
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
// 检查是否只是为了类型检查而创建bean, 不是的话将beanName添加到已创建bean容器中
// removeSingletonIfCreatedForTypeCheckOnly() 方法可移除只用来检查类型创建的bean
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
// 获取合并后的根 BeanDefinition,BeanDefintiton 跟类一样具有继承关系
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);

// 将依赖的bean提前创建
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dependsOnBean : dependsOn) {
getBean(dependsOnBean);
// 注册依赖 bean
registerDependentBean(dependsOnBean, beanName);
}
}
// 创建单例实例
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
try {
// 创建bean
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 创建原型实例
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// 创建自定义Scope实例, CustomScopeConfigurer / SimpleThreadScope
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {}
}
}

// 检查获取到的bean与参数 requiredType 是否匹配
if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return (T) bean;
}

doGetBean 方法里面调用了几个比较重要的方法:

  • getSingleton(String beanName);
  • getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd);
  • getMergedLocalBeanDefinition(String beanName);
  • getSingleton(String beanName, ObjectFactory singletonFactory);
  • createBean(String beanName, RootBeanDefinition mbd, Object[] args)。
3. getSingleton

我们先看看getSingleton(String beanName, ObjectFactory singletonFactory)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
synchronized (this.singletonObjects) {
// 先从缓存中获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 标记当前 beanName 正在创建
beforeSingletonCreation(beanName);
try {
// 调用 createBean()
singletonObject = singletonFactory.getObject();
} catch (BeanCreationException ex) {
throw ex;
} finally {
afterSingletonCreation(beanName);
}
// 添加到缓存中
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
4. createBean
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
protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException {
// Make sure bean class is actually resolved at this point.
// 将bean标签中class属性解析为Class类
resolveBeanClass(mbd, beanName);

// 检查 MethodOverrides, 主要是检查 MethodOverride 里面的 methodName 是否存在,或者是否重载
try {
mbd.prepareMethodOverrides();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException();
}

try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
// 给BeanPostProcessors机会返回一个代理bean, 替代掉目标bean
Object bean = resolveBeforeInstantiation(beanName, mbd);
if (bean != null) {
return bean;
}
} catch (Throwable ex) {
throw new BeanCreationException();
}
// 创建 bean
Object beanInstance = doCreateBean(beanName, mbd, args);
return beanInstance;
}
5. doCreateBean

看看真正创建Bean的方法 doCreateBean

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
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
// 如果是单例,看是否存在FactoryBean缓存,这个缓存主要是在调用 isTypeMatch() 或 getType() 检查类型匹配或者获取类型后缓存起来的
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 创建Bean实例,并返回Bean的包装实例
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
// post-processors 可以在此修改definition
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
mbd.postProcessed = true;
}
}

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 这里的目的就是解决循环依赖的问题
if (earlySingletonExposure) {
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}

// Initialize the bean instance.
Object exposedObject = bean;
try {
// 将xml文件配置的各种属性值填充到Bean中
populateBean(beanName, mbd, instanceWrapper);
// 初始化bean, 顺序:postProcessBeforeInitialization() -> afterPropertiesSet() ->
// 自定义的 init-method -> postProcessAfterInitialization()
exposedObject = initializeBean(beanName, exposedObject, mbd);
} catch (Throwable ex) {}

......

return exposedObject;
}

Bean实例化的大致流程基本上就是这样了,上面提到的一些重要方法这里还没具体看,我们下次再来分析一波。

在上文 Spring容器加载过程源码解析之默认标签解析 中,我们学习了默认标签的解析,今天我们来看看自定义的标签和属性是如何解析的。

提醒:本文是基于Spring 3.0.0.RELEASE 版本进行讲解的,其他版本可能稍有差异,在贴源码的时候,部分不影响流程的代码也在本文省略了

1. 常用的自定义标签和属性

自定义标签:<context:annotation-config/>, <context:component-scan base-package="com.xx.xx"/>, <mvc:annotation-driven>, <aop:aspectj-autoproxy/>等等都是自定义的标签;

自定义属性见得比较少,不过也有,如带 “p” 前缀的属性,是在spring-beansjar包中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 启用注解 -->
<context:annotation-config/>
<!-- 设置扫描的包 -->
<context:component-scan base-package="com.xxx"/>
<bean id="hello" class="com.xxx.Hello"/>
<!-- p:hello-ref 定义 HelloWorld 属性 hello,有后缀 -ref 表示bean的id -->
<!-- p:world 定义 HelloWorld 属性 world 的值 -->
<!-- p:world 会与下方的 property world 冲突 -->
<bean id="helloWorld" class="com.xxx.HelloWorld"
p:hello-ref="hello" p:world="world-value-p">
<property name="world" value="world-value"/>
</bean>
</beans>
2. parseCustomElement分析

我们先从自定义标签解析方法的起始位置 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions 看起

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
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 判断是否为默认命名空间下的标签
if (delegate.isDefaultNamespace(delegate.getNamespaceURI(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;
String namespaceUri = delegate.getNamespaceURI(ele);
// 判断是否为默认命名空间下的标签
if (delegate.isDefaultNamespace(namespaceUri)) {
// 默认标签解析方法
parseDefaultElement(ele, delegate);
} else {
// 自定义标签解析方法
delegate.parseCustomElement(ele);
}
}
}
} else {
// 自定义标签解析方法
delegate.parseCustomElement(root);
}
}

委托给了BeanDefinitionParserDelegate#parseCustomElement进行解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
// 根据命名空间获取对应的处理器 (命名空间处理解析器,下面会分析)
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 处理器进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
3. NamespaceHandler分析

结合上面类图,查看源码发现,命名空间处理器的主要逻辑是放在其子类NamespaceHandlerSupport中,而NamespaceHandlerSupport的子类主要是实现init方法,在里面注册该命名空间下标签对应的解析器,如ContextNamespaceHandlerinit方法,一个标签对应一个解析器。

1
2
3
4
5
6
7
8
9
10
11
public void init() {
// 注册解析器 BeanDefinitionParser
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}

NamespaceHandler调用解析器进行解析的方法是 parse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 查找对应的解析器进行解析
return findParserForElement(element, parserContext).parse(element, parserContext);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
// 根据标签名获取解析器
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
4. BeanDefinitionParser分析

我们拿常用的context:component-scan标签解析器看看

1
2
3
4
5
6
7
8
9
10
11
12
13
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 获取 base-package 值
String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

// Actually scan for bean definitions and register them.
// 扫描并注册
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

return null;
}

不同标签的具体解析逻辑就在parse方法中了。

5. NamespaceHandler的注册

我们在前面提过的命名空间处理解析器,用来解析命名空间对应的处理器

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

它的默认实现是DefaultNamespaceHandlerResolver

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
public NamespaceHandler resolve(String namespaceUri) {
// 获取所有处理器
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
} else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
} else {
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException(".....");
}
// 实例化处理器
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 初始化处理器,里面就是注册解析器
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
//..........此处省略部分源码
}
}

private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
// 加载 META-INF/spring.handlers 属性文件
// this.handlerMappingsLocation = "META-INF/spring.handlers"
Properties mappings = PropertiesLoaderUtils
.loadAllProperties(this.handlerMappingsLocation, this.classLoader);

Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>();
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
//..........此处省略部分源码
}
}
}
return this.handlerMappings;
}

代码中出现了"META-INF/spring.handlers"这么一个文件,打开spring-beans,spring-contextjar包下该文件,有如下内容:

1
2
3
4
5
6
7
8
9
10
11
spring-beans下

http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

spring-context下

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler

我们看到命名空间 http\://www.springframework.org/schema/context的处理器org.springframework.context.config.ContextNamespaceHandler的对应关系就是在这里确定的。

6. 自定义属性解析

我们来回顾下 BeanDefinitionParserDelegate#parseBeanDefinitionElement方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// bean标签比较复杂,委托给了BeanDefinitionParserDelegate进行解析,返回封装了含 BeanDefinition,beanName,aliases 的BeanDefinitionHolder
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 解析自定义属性或标签
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
// 注册到 BeanDefinitionRegistry,也就是DefaultListableBeanFactory中的 beanDefinitionMap 中
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}catch (BeanDefinitionStoreException ex) {}
// Send registration event. 发送bean注册事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)就是自定义属性的入口了

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
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) {
BeanDefinitionHolder finalDefinition = definitionHolder;
// Decorate based on custom attributes first.
NamedNodeMap attributes = ele.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
// 遍历属性
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
// Decorate based on custom nested elements.
NodeList children = ele.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
// 遍历子标签
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;
}

private BeanDefinitionHolder decorateIfRequired(
Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(node);
if (!isDefaultNamespace(namespaceUri)) {
// 这里跟前面的逻辑类似,就不重复讲了
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler != null) {
return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
}
//..........此处省略部分源码
}
return originalDef;
}

看看装饰方法NamespaceHandlerSupport#decorate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public BeanDefinitionHolder decorate(
Node node, BeanDefinitionHolder definition, ParserContext parserContext) {

return findDecoratorForNode(node, parserContext).decorate(node, definition, parserContext);
}

private BeanDefinitionDecorator findDecoratorForNode(Node node, ParserContext parserContext) {
BeanDefinitionDecorator decorator = null;
String localName = parserContext.getDelegate().getLocalName(node);
if (node instanceof Element) {
// 获取元素装饰器
decorator = this.decorators.get(localName);
} else if (node instanceof Attr) {
// 获取属性装饰器
decorator = this.attributeDecorators.get(localName);
}
//..........此处省略部分源码
return decorator;
}

再看看另外一个处理器 SimplePropertyNamespaceHandler的实现,这个处理器对应的是带 “p” 前缀的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
if (node instanceof Attr) {
Attr attr = (Attr) node;
String propertyName = parserContext.getDelegate().getLocalName(attr);
String propertyValue = attr.getValue();
MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues();
// 判断是否已经存在,默认标签优先解析
if (pvs.contains(propertyName)) {
parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " +
"both <property> and inline syntax. Only one approach may be used per property.", attr);
}
// 以 "-ref" 结尾,表示的是值为beanName,非具体值
if (propertyName.endsWith("-ref")) {
propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length());
pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue));
} else {
// 将值添加到definition中
pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue);
}
}
return definition;
}

相信大家看到这里应该明白自定义的标签或属性是怎么解析的了,大家可以尝试去定义一下自己的属性标签,这里就不带大家去实践了。

在上文 Spring容器加载过程源码解析之Resource解析流程 中,我们已经了解了整个解析流程,今天我们来具体分析下默认标签的解析。

提醒:本文是基于Spring 3.0.0.RELEASE 版本进行讲解的,其他版本可能稍有差异,在贴源码的时候,部分不影响流程的代码也在本文省略了

1.什么是默认标签和自定义标签
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
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 判断是否为默认命名空间下的标签
if (delegate.isDefaultNamespace(delegate.getNamespaceURI(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;
String namespaceUri = delegate.getNamespaceURI(ele);
// 判断是否为默认命名空间下的标签
if (delegate.isDefaultNamespace(namespaceUri)) {
// 默认标签解析方法
parseDefaultElement(ele, delegate);
} else {
// 自定义标签解析方法
delegate.parseCustomElement(ele);
}
}
}
} else {
// 自定义标签解析方法
delegate.parseCustomElement(root);
}
}

// 判断是否为默认命名空间下的标签
public boolean isDefaultNamespace(String namespaceUri) {
return (!StringUtils.hasLength(namespaceUri) || "http://www.springframework.org/schema/beans".equals(namespaceUri));
}

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
// 解析 import 标签
importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
// 解析 alias 标签
processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
// 解析 bean 标签
processBeanDefinition(ele, delegate);
}
}

isDefaultNamespace() 方法可以看出,在命名空间 http://www.springframework.org/schema/beans 下面定义的标签就是我们所说的默认标签,比如顶级标签 beans, 以及下面的 bean,import,alias等等其他标签。

而不在默认命名空间下的标签就是自定义标签了,比如我们最常见的带 context 前缀的标签:<context:annotation-config/>,<context:component-scan base-package="com.xx.xx"/>等。

2.bean 标签解析方法 processBeanDefinition

委托给了BeanDefinitionParserDelegate#parseBeanDefinitionElement进行解析,解析完成后再进行注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// bean标签比较复杂,委托给了BeanDefinitionParserDelegate进行解析,返回封装了含 BeanDefinition,beanName,aliases 的BeanDefinitionHolder
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 解析自定义属性或标签
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
// 注册到 BeanDefinitionRegistry,也就是DefaultListableBeanFactory中的 beanDefinitionMap 中
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}catch (BeanDefinitionStoreException ex) {}
// Send registration event. 发送bean注册事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

接下看看代理类 BeanDefinitionParserDelegate

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
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
// beans 标签下的 bean, containingBean 为空
return parseBeanDefinitionElement(ele, null);
}

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
// 获取属性 id 值,用作 beanName
String id = ele.getAttribute("id");
// 获取属性 name 值,用作别名, 多个别名可用‘,’或‘;’分割开来
String nameAttr = ele.getAttribute("name");
// 分割多个别名
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
// 属性id值作为 bean 的名称
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
// 如果未定义id, 则将 第一个别名作为 beanName
beanName = aliases.remove(0);
}
// beans 标签下的bean, 需要检查beanName和aliases的在当前xml配置文件中的唯一性
// bean 标签中嵌套的 bean 不需要检查
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
// 解析标签的具体操作
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
// 下面主要是没有定义 beanName 时,名字自动生成的逻辑
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
} else {
beanName = this.readerContext.generateBeanName(beanDefinition);

String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
}
//..........此处省略部分源码
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
// 返回 holder
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}

上面这部分主要是处理名字和别名的逻辑,下面看下解析的具体操作

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
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));

String className = null;
// 解析 class 属性
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
String parent = null;
// 解析 parent 属性
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
// 创建GenericBeanDefinition实例
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 解析其他属性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 解析meta标签
parseMetaElements(ele, bd);
// 解析lookup-method标签
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 解析replaced-method标签
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// 解析constructor-arg标签
parseConstructorArgElements(ele, bd);
// 解析property标签
parsePropertyElements(ele, bd);
// 解析qualifier标签
parseQualifierElements(ele, bd);

bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
//..........此处省略部分源码
finally {
this.parseState.pop();
}
return null;
}

上面就是解析的具体过程了,更具体的解析细节比较繁琐,不过逻辑简单,这里就不一一贴出来了,小伙伴们可以自己跟进去看看。

3.import 标签解析方法 importBeanDefinitionResource
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
protected void importBeanDefinitionResource(Element ele) {
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
if (!StringUtils.hasText(location)) {
return;
}
// Resolve system properties: e.g. "${user.dir}"
// 解析占位符
location = SystemPropertyUtils.resolvePlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
// 判断是绝对还是相对路径
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
} catch (URISyntaxException ex) {}

// Absolute or relative?
if (absoluteLocation) {
try {
// 确定是绝对路径后,直接交给 XmlBeanDefinitionReader 进行定位,加载,解析
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
} catch (BeanDefinitionStoreException ex) {}
} else {
try {
int importCount;
// 创建与当前资源同一父路径的相对资源
Resource relativeResource = getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
// 资源存在,直接加载
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
} else {
// 资源不存在,表明路径是含有通配符的路径
String baseLocation = getReaderContext().getResource().getURL().toString();
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
}
//..........此处省略部分源码
}
Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
// 发送import事件
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

import标签解析比较简单,上面提到的资源 定位,加载方法: loadBeanDefinitions, 在Spring容器加载过程源码解析之Resource定位加载一文中有详细分析,想了解的小伙伴可以进去看看。

4.alias 标签解析方法 processAliasRegistration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected void processAliasRegistration(Element ele) {
String name = ele.getAttribute(NAME_ATTRIBUTE);
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
if (!StringUtils.hasText(name)) {
valid = false;
}
if (!StringUtils.hasText(alias)) {
valid = false;
}
if (valid) {
try {
// 注册别名
getReaderContext().getRegistry().registerAlias(name, alias);
} catch (Exception ex) {}
// 发送alias注册事件
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}

alias标签跟bean标签下的alias属性都一样是bean的别名,那有什么作用呢?最简单的例子,比如,配置文件存在第三方jar包中,我们无法修改,但是又想通过特定的别名进行访问,这时候alias标签就派上用场了。

1
2
3
4
5
// jar包中配置
<bean id="hello" name="hello-alias-1, hello-alias-2" class=“com.xx.xx"/>
// 自己的配置
<alias name="hello" alias="hello-alias-3"/>
<alias name="hello" alias="hello-alias-4"/>

按照上面这么配置,hello 就有4个别名了。

好了,今天的默认标签解析就分析到这里了,下篇文章我们再分析自定义标签是如何解析的。

在上文 Spring容器加载过程源码解析之Resource定位加载 中,我们已经将资源路径解析为Resource了,今天我们来分析下整个解析流程。

提醒:本文是基于Spring 3.0.0.RELEASE 版本进行讲解的,其他版本可能稍有差异,在贴源码的时候,部分不影响流程的代码也在本文省略了

1. XmlBeanDefinitionReader 分析
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
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
//..........此处省略部分源码
if (resourceLoader instanceof ResourcePatternResolver) {
try {
// 这里才是真正将 资源路径 解析为Resource的地方
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 开始解析
int loadCount = loadBeanDefinitions(resources);
//..........此处省略部分源码
}
}
//..........此处省略部分源码
}

public int loadBeanDefinitions(Resource[] resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
// 遍历所有资源 进行解析
counter += loadBeanDefinitions(resource);
}
return counter;
}

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
//..........此处省略部分源码
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 从Resource获取输入流进行解析
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
//..........此处省略部分源码
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
// 通过 documentLoader 将资源转换为 Document 对象
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
return registerBeanDefinitions(doc, resource);
}
//..........此处省略部分源码
}

// EntityResolver 由 BeansDtdResolver 和 PluggableSchemaResolver 组成
// 用来在 classpath 下搜寻 schema 和 DTD 文件
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
// Determine default EntityResolver to use.
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
} else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// Read document based on new BeanDefinitionDocumentReader SPI.
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 委托 BeanDefinitionDocumentReader 解析document
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}

protected XmlReaderContext createReaderContext(Resource resource) {
if (this.namespaceHandlerResolver == null) {
// 创建默认命名空间处理解析器
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, this.namespaceHandlerResolver);
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
// 用来解析 META-INF/spring.handlers 目录下对应的处理器,自定义标签的时候也会使用到,我们后面会详细分析
return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}

从上述代码中,我们看到XmlBeanDefinitionReader完成了如下工作:

  1. 由内部的ResourceLoader去获取所有符合条件的Resource (上文已重点分析过 Spring容器加载过程之源码解析之Resource定位加载);
  2. Resource中获取流,转化为方便解析的Document对象;
  3. 委托BeanDefinitionDocumentReader来解析 Document,所以实际的bean注册工作也是由它来完成。
2. DefaultBeanDefinitionDocumentReader 分析
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
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
// 获取根元素,这里一般就是 beans 标签
Element root = doc.getDocumentElement();
// 创建解析bean的代理类
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
// 与下方的 postProcessXml 一样,在这里都没有实现,可用来扩展自定义标签
preProcessXml(root);
// 解析根元素
parseBeanDefinitions(root, delegate);
postProcessXml(root);
}

protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) {
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
// 初始化默认的设置,即 beans 标签的属性
// Initialize the default lazy-init, autowire, dependency check settings, init-method, destroy-method and merge settings
delegate.initDefaults(root);
return delegate;
}

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 判断是否为默认命名空间下的标签
if (delegate.isDefaultNamespace(delegate.getNamespaceURI(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;
String namespaceUri = delegate.getNamespaceURI(ele);
// 判断是否为默认命名空间下的标签
if (delegate.isDefaultNamespace(namespaceUri)) {
// 默认标签解析方法
parseDefaultElement(ele, delegate);
} else {
// 自定义标签解析方法
delegate.parseCustomElement(ele);
}
}
}
} else {
// 自定义标签解析方法
delegate.parseCustomElement(root);
}
}

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
// 解析 import 标签
importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
// 解析 alias 标签
processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
// 解析 bean 标签
processBeanDefinition(ele, delegate);
}
}

来到这里,Resource 解析的整个流程就清晰了许多,默认标签和自定义标签的具体解析我们将在后面的文章再来分析。

在上文 如何手动启动Spring容器 中,我们知道了可以通过传入资源文件来启动容器,如果将applicationContext.xml替换为绝对路径就启动不了,报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//启动容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml")

//更换为绝对路径,启动失败
ApplicationContext context = new ClassPathXmlApplicationContext(
"E:\\IDEA_workspace\\SpringLearningDemo\\spring-java\\src\\main\\resources\\applicationContext.xml");

//报 FileNotFoundException:Caused by: java.io.FileNotFoundException: class path resource [E:/IDEA_workspace/SpringLearningDemo/spring-java/src/main/resources/applicationContext.xml] cannot be opened because it does not exist

//使用 FileSystemXmlApplicationContext 正常启动
ApplicationContext context = new FileSystemXmlApplicationContext(
"E:\\IDEA_workspace\\SpringLearningDemo\\spring-java\\src\\main\\resources\\applicationContext.xml");

// 启动成功
ApplicationContext context = new FileSystemXmlApplicationContext(
"classpath:applicationContext.xml");
// 启动成功
ApplicationContext context = new ClassPathXmlApplicationContext(
"file:E:\\IDEA_workspace\\SpringLearningDemo\\spring-java\\src\\main\\resources\\applicationContext.xml");

通过字面意思,可以看出ClassPathXmlApplicationContext, 传入的是classpath目录下的资源文件, FileSystemXmlApplicationContext, 传入的是文件系统下资源文件(即文件绝对路径), 我们在资源文件前面加上 classpath 或 file,启动试试,发现也都成功了,看来资源的定位没有上面说的那么简单,还跟资源路径前缀有关。

提醒:本文是基于Spring 3.0.0.RELEASE 版本进行讲解的,其他版本可能稍有差异,在贴源码的时候,部分不影响流程的代码也在本文省略了

下面带着疑问来看看Spring资源到底是怎么定位和加载进来的。进入ClassPathXmlApplicationContext源码,按下面路径走, 找到getResources方法:

refresh() -> obtainFreshBeanFactory() -> refreshBeanFactory() -> loadBeanDefinitions(DefaultListableBeanFactory beanFactory)

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
// AbstractXmlApplicationContext.java
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 创建 XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 传入ResourceLoader
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader);

loadBeanDefinitions(beanDefinitionReader);
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 获取容器启动时,传入的资源文件路径
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}

// AbstractBeanDefinitionReader.java
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
//..........此处省略部分源码
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// 这里才是真正将 资源路径 解析为Resource的地方
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
//..........此处省略部分源码
}
}
//..........此处省略部分源码
}

从上面可以看出,AbstractXmlApplicationContextBean的装载任务委派给了XmlBeanDefinitionReader, 而ResourceLoader负责将xml解析为Resource

先看看类结构图

从上面可以看到,Spring为了统一不同类型的资源访问,把所有资源都抽象成Resource接口, 屏蔽不同资源之间的差异,其实现了InputStreamSource, 这样所有Resource都可以通过getInputStream()获取InputStream

其中资源定位路径 -> Resource 这个过程是由 ResourceLoader 这个类来完成的,我们暂且称之为: 资源定位器, 其默认实现是 DefaultResourceLoader

ResourcePatternResolver 继承自 ResourceResolver ,扩展了一个能通过路径模式匹配定位的方法: Resource[] getResources(String locationPattern),其路径模式支持以 classpath / classpath* 或 URI协议名(例如 http、file、jar:file)为其前缀,还支持 Ant风格 的匹配模式。

看看 ResourcePatternResolver 的实现类 PathMatchingResourcePatternResolver

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
// 构造方法创建的 DefaultResourceLoader
private final ResourceLoader resourceLoader;
// Ant风格 路径匹配器
private PathMatcher pathMatcher = new AntPathMatcher();

public Resource[] getResources(String locationPattern) throws IOException {
if (locationPattern.startsWith("classpath*:")) {
// "classpath*:" 后的路径是否含有通配符 “*” 或 “?”,即是否为路径模式
if (getPathMatcher().isPattern(locationPattern.substring("classpath*:".length()))) {
// eg: classpath*:a/b/applicationContext-*.xml
// 根据路径模式 查找所有匹配的资源
return findPathMatchingResources(locationPattern);
} else {
// eg: classpath*:a/b/applicationContext-dao.xml 或 classpath*:a/b/
// 根据确定路径 在所有classpath中(包含所有jar包)查找资源
return findAllClassPathResources(locationPattern.substring("classpath*:".length()));
}
} else {
// 第一个 ":" 后的路径是否含有通配符 “*” 或 “?”,即是否为路径模式
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// 根据路径模式 查找所有匹配的资源
return findPathMatchingResources(locationPattern);
} else {
// 加载单个资源,后面再详细讲讲 getResource 这个方法
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}

// 根据路径模式 查找所有匹配的资源
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
// 获取到一个不含通配符的根目录,如 classpath*:a/b/*/applicationContext-*.xml 返回 classpath*:a/b/
String rootDirPath = determineRootDir(locationPattern);
// 返回 */applicationContext-*.xml
String subPattern = locationPattern.substring(rootDirPath.length());
// 递归调用getResources(), 获取的符合根目录的所有资源
Resource[] rootDirResources = getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet<Resource>(16);
// 遍历根目录资源,将匹配的资源添加到 result 中
for (Resource rootDirResource : rootDirResources) {
// jar:file:开头的为jar包资源
if (isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
} else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
} else {
// 其他类型资源走这里
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
return result.toArray(new Resource[result.size()]);
}

protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
throws IOException {
File rootDir = rootDirResource.getFile().getAbsoluteFile();
//..........此处省略部分源码
return doFindMatchingFileSystemResources(rootDir, subPattern);
}

protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
// retrieveMatchingFiles 流程比较长就不贴出来了,有兴趣的朋友可以debug进去看看
Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
for (File file : matchingFiles) {
// 符合条件的路径 此时就解析为Resource了
result.add(new FileSystemResource(file));
}
return result;
}

上面就是PathMatchingResourcePatternResolver路径模式匹配的基本逻辑。加载单个资源的任务是委托给了构造方法里面创建的 DefaultResourceLoader

DefaultResourceLoader 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Resource getResource(String location) {
// 以 classpath: 开头的返回 ClassPathResource
if (location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()), getClassLoader());
} else {
try {
// 能够解析为URL的返回 UrlResource
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException ex) {
// 不能够解析为URL的 由getResourceByPath返回
return getResourceByPath(location);
}
}
}

protected Resource getResourceByPath(String path) {
// 默认返回 ClassPathContextResource
return new ClassPathContextResource(path, getClassLoader());
}

从上面的类结构图,我们看到 FileSystemXmlApplicationContextClassPathXmlApplicationContext 都是 DefaultResourceLoader 的子类,区别在于前者重写了 getResourceByPath 方法

1
2
3
4
5
6
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}

那么 不带前缀的 路径或者 自定义前缀 的路径能否定位成功,关键在于getResourceByPath方法了,回到文章开始 传入的路径:

applicationContext.xmlE:\\IDEA_workspace\\SpringLearningDemo\\spring-java\\src\\main\\resources\\applicationContext.xml

走的是 DefaultResourceLoader 默认的 getResourceByPath方法,返回的classpath里面的资源,所以后者在ClassPathXmlApplicationContext里面是启动不了的,反之前者在FileSystemXmlApplicationContext也启动不了

classpath:applicationContext.xmlfile:E:\\IDEA_workspace\\SpringLearningDemo\\spring-java\\src\\main\\resources\\applicationContext.xml

都带有前缀,所以跟getResourceByPath无关,也就是跟 FileSystemXmlApplicationContext 和 ClassPathXmlApplicationContext 无关,传入哪个Context都能启动成功

总结起来, Spring的资源路径填写方式如下:

前缀 示例 说明
classpath: classpath:a/c.xml 从classpath中加载,存在多个资源则返回最先加载的那个资源,易导致资源加载不进来的问题
classpath*: classpath*:a/c.xml 从classpath中加载,返回全部符合条件的资源,需要遍历所有classpath, 因此加载效率低
file: 或 jar:file: 等URI协议名 file:d:\b\f.xml 作为URL进行加载
没有前缀 a/c.xml 或 d:\b\f.xml 根据context的getResourceByPath方法判断

Ant风格 的匹配模式

“ ? ”:匹配一个字符, 如 a?.xml 匹配 ab.xml

“ * ”:匹配零个或多个字符串,如“a/ * /c.xml”将匹配“a/b/c.xml”,但不匹配匹配“a/c.xml”;而“a/c-*.xml”将匹配“a/c-dao.xml”

“ ** ”:匹配路径中的零个或多个目录,如“a/ ** /c.xml”将匹配“a /c.xml”,也匹配“a/b/b/c.xml”;而“a/b/c- ** .xml”将匹配“a/b/c-dao.xml”,即把“ ** ”当做两个“ * ”处理。

这就是Spring容器中资源的定位,然后通过 ResourcegetInputStream() 加载进内存进行解析,至于如何解析,就请听下回分解啦。

工作中,我想大家最熟悉的Spring容器启动方法,就是在web环境下,通过在web.xml中配置如下代码进行启动。

1
2
3
4
5
6
7
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

那么,离开了web环境,想单独的启动一个Spring容器该怎么做呢,其实也很简单,有两种方式,直接看代码:

1. 手动启动

目录结构:

pom.xml

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
<properties>
<spring.version>3.0.0.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 日志依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.13</version>
</dependency>
<!-- 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- spring单元测试依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<bean id="helloWorld-id" name="helloWorld" class="com.bing.lan.spring.HelloWorld"/>

</beans>

日志配置: logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<Configuration debug="true">
<contextName>spring</contextName>
<property name="NORMAL_PATTERN"
value=" %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %logger{100} - %msg%n"/>

<Appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<Layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>${NORMAL_PATTERN}</Pattern>
</Layout>
</Appender>

<ROOT level="DEBUG">
<Appender-ref ref="STDOUT"/>
</ROOT>

</Configuration>

HelloWorld.java

1
2
3
4
5
6
7
8
9
10
11
public class HelloWorld {

private String name = "OOPcoder";

@Override
public String toString() {
return "HelloWorld{" +
"name='" + name + '\'' +
'}';
}
}

SpringStartup.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.bing.lan.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringStartup {

public static void main(String[] args) {
// 手动启动spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorld");
System.out.println("main(): " + helloWorld);
}
}

启动main函数,容器就启动了。

2. 通过 junit 来启动

在上面这些类的基础上再添加一个测试类

SpringTest.java

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
package com.bing.lan.spring;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class SpringTest {

@Autowired
BeanFactory beanFactory;

@Autowired
ApplicationContext applicationContext;

@Test
public void test() {
HelloWorld helloWorld = (HelloWorld) applicationContext.getBean("helloWorld");
System.out.println("main(): " + helloWorld);

helloWorld = (HelloWorld) beanFactory.getBean("helloWorld");
System.out.println("main(): " + helloWorld);
}
}

运行test(),容器启动成功。

学会了怎么启动,有啥好处呢,好处很多,比如

  1. 可以脱离web环境测试我们的 service / mapper 层,极大的提高开发效率;

  2. 还可以debug进Spring源码里学习各种原理,这对我们小白来说,是非常友好的,因为这只是一个单纯的Spring, 没有其他框架的干扰。