应用上下文初始化简要流程一文中主要简要的描述了Spring的上下文加载的简要流程。文本是对《应用上下文初始化简要流程》的补充,主要简单的分析了BeanFactory的初始化以及BeanDefinition加载的过程.
我们知道,Spring通过refresh操作重建了ApplicaitonContext
,在这个过程中同时也构建了默认的BeanFactory
以及加载了BeanDefinition
。AbstractApplicationContext
类在refresh()
方法中调用了obtainFreshBeanFactory
, 此方法主要负责重建BeanFactory
以及加载BeanDefinition
。 下面我们对此方法进行简要分析。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
可以看出,此方法的主要逻辑由refreshBeanFactory()
方法实现,而refreshBeanFactory()
方法的默认实现由AbstractRefreshableApplicationContext
来提供,代码如下:
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
整个refreshBeanFactory()
的逻辑是比较清晰的,首先判断是否已存在BeanFactory
,如果已存在则销毁:
destroyBeans()
清除Bean引用的缓存closeBeanFactory()
释放已创建的BeanFactory
其次调用createBeanFactory
建立默认的BeanFactoryDefaultListableBeanFactory
————一个基于Bean Definition对象的成熟的Bean工厂;
当BeanFactory建立好之后,进行一项简单的配置customizeBeanFactory()
, 以决定在加载BeanDefinition的过程中是否允许循环引用以及是否允许对已有BeanDefinition进行覆写.
最后,方法loadBeanDefinitions()
才是重头戏————BeanDefinition
的加载过程。
实现loadBeanDefinitions()
的子类有多种,如AbstractXmlApplicationContext
类提供了基于XML的加载实现,AnnotationConfigWebApplicationContext
类提供了在webapp的场景下基于注解配置的加载实现,XmlWebApplicationContext
类提供了在webapp场景下基于xml配置的加载实现,XmlPortletApplicationContext
提供了在portalet
下基于xml配置的加载实现,GroovyWebApplicationContext
类提供了基于groovy
脚本配置的加载实现。当然也可以自己实现此方法.
此处,我们主要分析基于xml的加载过程。代码如下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 第一行
beanDefinitionReader.setEnvironment(this.getEnvironment()); //第二行
beanDefinitionReader.setResourceLoader(this); //第三行
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 第四行
initBeanDefinitionReader(beanDefinitionReader); // 第五行
loadBeanDefinitions(beanDefinitionReader); // 第六行
}
第一行代码中实例化了一个XmlBeanDefinitionReader
对象,此对象用于读取xml的bean定义,实际上它会将对于xml的处理委托给BeanDefinitionDocumentReader
接口。这个对象的构造器需要传入BeanDefinitionRegistry
对象,因此此处beanFactory
作为一个BeanDefinitionRegistry
对象被使用;
在XmlBeanDefinitionReader
的构造器中调用了它的父抽象类AbstractBeanDefinitionReader
的构造器,在这个构造器中,分别为XmlBeanDefinitionReader
对象的属性resourceLoader
和Environment
赋值; 如果beanFactory
没有实现ResourceLoader
接口则会创建PathMatchingResourcePatternResolver
实例作为resourceLoader
; 如果beanFactory
没有实现EnvironmentCapable
则会创建StandardEnvironment
作为environment
; 此处beanFctory
的类型为DefaultListableBeanFactory
,因此不具备以上条件,故均采用创建对象;
PathMatchingResourcePatternResolver
类代理了DefaultResourceLoader
,对于通过简单的location获取Resource
由DefaultResourceLoader
实现,其余复杂location加载Resource
由其自己实现;此处采用的设计模式为静态代理
第二行代码为beanDefinitionReader
的environment
赋值,值为StandardEnvironment
实例(非web应用的环境)
第三行代码为beanDefinitionReader
的resourceLoader
赋值,值为当前上下文(当前对象的父类继承了DefaultResourceLoader
)
第四行代码为beanDefinitionReader
的entityResolver
赋值,值为ResourceEntityResolver
的实例.此类作为EntityResolver
的实现主要是通过ResourceLoader
来尝试解析实体引用; 此类又集成了DelegatingEntityResolver
所以也可以查找DTD和XSD。
第五行代码initBeanDefinitionReader(beanDefinitionReader)
主要是对beanDefinitionReader
进行初始化配置,默认是空实现,主要是用于子类覆盖此方法实现自定义XmlBeanDefinitionReader
的初始化配置,例如打开或者关闭XML验证,配置不同的XmlBeanDefinitionParser
实现
第六行代码loadBeanDefinitions(beanDefinitionReader)
就是BeanDefinition
的主要加载过程了.它使用指定的XmlBeanDefinitionReader
进行加载BeanDefinition
. 这个方法仅负责加载或者注册BeanDefinition
,而生命周期是通过bean工厂的refreshBeanFactory
方法管理. loadBeanDefinitions
代码如下:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources(); // 1
if (configResources != null) {
reader.loadBeanDefinitions(configResources); // 2
}
String[] configLocations = getConfigLocations(); // 3
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations); // 4
}
}
我们可以看出,对于BeanDefinition
的加载最终是使用XmlBeanDefinitionReader
的实例进行加载的,此处只是对其指定了加载的资源或者资源的位置。
1
处获取是否有配置好的Resource
,如果有直接交由reader
进行加载(默认实现中是没有配置好的Resource
,但可以覆写getConfigResources()
进行配置);
3
处获取配置的XML位置,如果没有配置xml的位置,则去默认的地方获取xml;当获取到xml路径的字符串后,则交由reader
进行解析加载(4
处),代码如下:
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location); // 1
}
return counter;
}
此方法位于AbstractBeanDefinitionReader
类中,主要针对字符串路径进行解析,最终得到Resource
对象,然后将Resource
交由XmlBeanDefinitionReader
进行处理,所以针对xml的资源加载的最终位置还是位于XmlBeanDefinitionReader
中;
我们来看下在Spring中如何将xml位置的字符串转换为Resource
的,代码如下:
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
return findPathMatchingResources(locationPattern);
}else {
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(":") + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
return findPathMatchingResources(locationPattern);
}
else {
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
classpath*:
开头,则 classpath*:dir/**
等,则调用findPathMatchingResources
方法查找符合配置的表达式的资源classpath*:/org/abc/
, 则调用findAllClassPathResources
加载此位置下的所有文件.classpath*:
开头,去掉资源访问协议,如果协议是war:
,则需要截取*/
后边的内容,否则直接去:
后边的内容, 根据其: findPathMatchingResources
加载资源/applition.xml
通过以上步骤,一个xml位置的字符串就会形成Spring中统一的资源抽象Resource
, 形成Resource
的一个或者多个实例之后,则对其进行解析与加载bean definition. 对Resource
进行解析和加载是通过接口BeanDefinitionReader
定义的,对xml文件加载的实现则由其实现类XmlBeanDefinitionReader
实现. 关键代码:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) { // 标注1
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); // 标注2
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
值得注意的是,在解析Resource之前,Spring会将此Resource存储于当前线程的局部变量ThreadLocal
中;在解析之后会将Resource从resourcesCurrentlyBeingLoaded
移除。 但这个操作的目的是什么呢?我们从两方面来分析:
Set
结构防止重复解析,注意看标注1
处的代码,我们知道Set内元素唯一,如果已经将同一个Resource
多次添加至Set中时,除了第一次之外其余都不会添加成功的;ThreadLocal
确保线程安全。 虽然第一步通过Set防止了重复解析,但前提是单线程情况下; 如果此方法被多线程调用时,那么还是会有几率出现一个Resource
被多次解析; 传统的解决方式是使用synchronized
进行同步,但加锁会导致解析效率低下,所以此处将其放置于ThreadLocal
对象中,确保每个线程只能访问自己的Set,从而既保证了性能,又解决了线程不安全的问题.在确保单个线程内Resource不会被重复消费的情况下,Spring开始了对Resource的解析,标志2
处代码实现:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (...) {
// ...
}
我们可以看到通过doLoadDocument
方法将其解析为Document
对象,然后通过registerBeanDefinitions
方法对Document
对象进行提取与BeanDefinition
的注册.
这里的
Document
是符合w3c定义的标准xml文档; 我们知道xml解析一般有DOM
方式(document)、SAX
方式(Simple API for XML)以及StAX
(Streaming API for xml)方式;而此处的Resource不会包含太大量的信息,所以采用了DOM
方式,即一次将整个XML都加载到内存中进行解析.
当我们得到Document
之后是如何对其进行解析和注册的呢?代码:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 1
int countBefore = getRegistry().getBeanDefinitionCount(); // 2
documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 3
return getRegistry().getBeanDefinitionCount() - countBefore; // 4
}
这个方法的作用就是对给予的Document
中的BeanDefinition进行注册。
第一行代码创建了一个BeanDefinitionDocumentReader
对象,此对象为BeanDefinition注册到容器的一个SPI(Service Provider Interface), 它具有一个默认的实现DefaultBeanDefinitionDocumentReader
; 此外这里还有一个比较有趣的点,产生此对象的方式不是直接通过new获取的,而是通过反射获取此对象的实例:
BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass))
这样做的主要的目的还是为了方便后续扩展,也为自定义注册规则成为可能.
第二行代码是获取对此Document解析前的已注册的beandefinition的数量
第三行代码是对此Document进行解析和注册的过程,具体的实现位于DefaultBeanDefinitionDocumentReader
类中,代码:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate); // 标注1
postProcessXml(root);
this.delegate = parent;
}
方法doRegisterBeanDefinitions
会将root中的每个bean definition都进行注册,对于root的解析的逻辑委托BeanDefinitionParserDelegate
对象实现. 解析的root的过程从标注1
处的方法开始:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) { // 标注1
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)) {
parseDefaultElement(ele, delegate); // 标注2
}
else {
delegate.parseCustomElement(ele); // 标注3
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
标注1
判断root元素的namespace是否为http://www.springframework.org/schema/beans
, 如果是,则获取root的的子节点,如果子节点的namespace为http://www.springframework.org/schema/beans
, 则交由方法parseDefaultElement
解析,如果不是则交由委托类解析delegate.parseCustomElement
(比如
,以及自定义标签等);
命名空间http://www.springframework.org/schema/beans
主要包含了4类节点,
, 如果root元素或者其子元素为此4类节点,则注解进入标注2
的方法,进行解析和注册,代码如下:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // 标注1
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 标注2
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // 标识3
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // 标注4
// recurse
doRegisterBeanDefinitions(ele);
}
}
根据代码我们可知:
import
标签,通过importBeanDefinitionResource
方法进行解析,大致的逻辑是通过resource
属性取得配置文件的位置,然后将其转换为Resource
,交由XmlBeanDefinitionReader
来解析.importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
alias
标签,通过processAliasRegistration
方法进行解析,在进行进行是否可重写校验和循环引用校验后,将alias
标签的name
和alias
属性添加到上下文的aliasMap
中;beans
标签,进行迭代调用doRegisterBeanDefinitions
方法bean
标签,通过processBeanDefinition
进行处理和注册,代码如下: protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); //标注1
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); // 标注2
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); // 标注3
}
}
可以看出,首先通过delegate的方法parseBeanDefinitionElement
将element解析为BeanDefinitionHolder
对象,然后对此对象进行必须得装配后,调用标注2
处的方法将其注册到上下文中;
那么BeanDefinitionHolder
到底是一个什么对象呢?BeanDefinitionHolder
对象通过name
和alias
持有BeanDefinition
,最终注册的时候会将其持有的BeanDefinition
注册到上下文中;
下面主要看下BeanDefinitionHolder
持有的BeanDefinition
的产生过程:
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName)); // 入栈,用于跟踪解析过程
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); // 提取class属性
}
try {
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE); // 提起parent属性
}
// 根据class属性和parent属性创建BeanDefinition,
// 首先通过类加载器寻找class类并将其设置于beanClass属性
// 如果类加载器暂时无法发现此类,则将class设置于beanClassName属性上
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 将元素中定义的bean的属性提取并设置到AbstractBeanDefinition中,
// 提取的属性有scope abstract lazy-init autowire denpendency-check
// depends-on autowire-candidate parimary init-method destory-method
// factory-method factory-bean
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
// 提取desciption标签内容
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 解析meta标签中配置的k-v数据
parseMetaElements(ele, bd);
// 解析配置的lookup-method标签,主要用于无参方法覆盖
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 解析配置的replaced-method标签,主要用于有参方法替换
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// 解析配置的construnctor-arg标签,主要用于声明有参构造函数的参数
parseConstructorArgElements(ele, bd);
// 解析配置的property标签
parsePropertyElements(ele, bd);
// 解析配置的qualifier标签
parseQualifierElements(ele, bd);
// 设置resource
bd.setResource(this.readerContext.getResource());
// 用于设置 允许工具控制元数据如何附加到bean definition的策略
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
return null;
}
当生成AbstractBeanDefinition后,还得给起一个唯一的名字,BeanDefinitionReaderUtils.generateBeanName
代码如下:
public static String generateBeanName(
BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
throws BeanDefinitionStoreException {
String generatedBeanName = definition.getBeanClassName();
if (generatedBeanName == null) {
if (definition.getParentName() != null) {
generatedBeanName = definition.getParentName() + "$child";
}
else if (definition.getFactoryBeanName() != null) {
generatedBeanName = definition.getFactoryBeanName() + "$created";
}
}
if (!StringUtils.hasText(generatedBeanName)) {
throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " +
"'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
}
String id = generatedBeanName;
if (isInnerBean) {
// Inner bean: generate identity hashcode suffix.
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
}
else {
// Top-level bean: use plain class name.
// Increase counter until the id is unique.
int counter = -1;
while (counter == -1 || registry.containsBeanDefinition(id)) {
counter++;
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
}
}
return id;
}
现在名字有了,BeanDefinition也具备了,就剩下将其注册到应用上下文了。
// BeanDefinitionReaderUtils
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 标注1
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
调用标注1
的地方进行注册,实际上是由DefaultListableBeanFactory
的registerBeanDefinition
来实现其逻辑的,在此方法最终会做以下事情:
BeanDefinition
缓存至beanDefinitionMap
beanDefinitionNames
至此,xml中定义的BeanDefinition
元数据被注册至应用上下文中.