注 :源码对应版本为 4.2.8.RELEASEjava
引入Spring依赖的时候,是否发现spring依赖有spring-beans和spring-context两个jar,spring容器的顶层接口BeanFactory在spring-beans,而咱们经常使用的ApplicationContext则在spring-context中定义。目前的认知是,ApplicationContext是在BeanFactory的基础上,提供了不少容器上下文的功能。那么到底BeanFactory提供了哪些功能,ApplicationContext在这些功能的基础上又额外提供了哪些上下文功能,其中的实现原理是什么?node
首先抛开ApplicationContext,咱们单独来看一下spring-beans模块中提供了哪些接口和功能:web
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd"> <bean class="lujing.sample.bean.TargetBean" /> </beans>
public class XmlBeanFacotyTest { public static void main(String[] args) { //指定xml文件 Resource resource = new ClassPathResource("spring/applicationContext-beanFactory.xml"); //实例化一个XmlBeanFactory XmlBeanFactory beanFactory = new XmlBeanFactory(resource); //这样就能够工做啦,能够getBean了 TargetBean targetBean = beanFactory.getBean(TargetBean.class); System.out.println(targetBean.getName()); } }
查看XmlBeanFactory的源码能够看到,也比较简单:spring
@Deprecated @SuppressWarnings({"serial", "all"}) public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); /** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }
其实就是2个核心类,分别是 DefaultListableBeanFactory 和 XmlBeanDefinitionReader ,spring自己标识了XmlBeanFactory废弃,取而代之使用 DefaultListableBeanFactory 和 XmlBeanDefinitionReader,因此以上代码能够写成:设计模式
public class BeanFactoryTest { public static void main(String[] args) { Resource resource = new ClassPathResource("spring/applicationContext-beanFactory.xml"); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(null); new XmlBeanDefinitionReader(beanFactory).loadBeanDefinitions(resource); TargetBean targetBean = beanFactory.getBean(TargetBean.class); System.out.println(targetBean.getName()); } }
显而易见,spring中xml解析是由 XmlBeanDefinitionReader 来完成的。剥离了xml这一层具体的实现(也能够有基于Annotation的实现等)缓存
从上述代码中能够看到,xml到解析工做经过 loadBeanDefinitions方法完成:安全
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
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()); } //这里使用ThreadLocal一方面为了线程安全考虑,另外一方面提供存储 //使用 // private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = // new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently // being loaded"); //而不用 Set<EncodedResource> currentResources = new HashSet<EncodedResource>(4); //是由于该方法可能会被多线程调用。为了记录当前正在解析的Resource文件 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { //初始化set并设置线程上文 currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } //利用Set的add方法,判断重复的定义 if (!currentResources.add(encodedResource)) { 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()); } 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(); } } }
从上面的代码中能够看到,该方法主要作了:网络
利用线程上下文值,记录当前线程正在解析的xml文件。检查import标签可能引入的循环解析问题多线程
从这个方法中咱们也能够借鉴两种经常使用的写法:架构
1. 关于ThreadLocal的用法,在使用get(),set()使用完成之后,必需要在finally代码块中 remove()掉
2. loadBeanDefinitions 和 doLoadBeanDefinitions的方法命名,咱们发现spring中这类命名特别多。do开头的方法每每是正真的业务实现,一些外围处理都环绕在外面。这样的写法看起来真的特别舒服,一层一层的,极大的减小了代码的理解复杂度。同时也为扩展提供了很好的架构。通常spring中do开头的方法都是protected的,显然告诉spring的使用者,这是一个扩展点。这也是设计模式中 “模板模式”很好的体现。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //生成XML Document 文档对象 Document doc = doLoadDocument(inputSource, resource); //注册Bean定义 return registerBeanDefinitions(doc, resource); } //catch 各类异常,统一包装为 XmlBeanDefinitionStoreException //这里就不贴代码了 }
生成XML文档对象的时候,使用了DocumentLoader实现类来完成,底层主要是利用JAXP完成从xml解析问Document对象:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { //private DocumentLoader documentLoader = new DefaultDocumentLoader(); return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
Bean的解析使用BeanDefinitionDocumentReader
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
spring底层经过JAXP技术,完成从xml到Document对象的解析。
在xml解析中有一点比较重要,就是xml的验证,它主要分为DTD验证和XSD验证两种。不管哪种验证,java都须要加载相应的DTD或XSD文件,默认是直接从xml中对应的地址下载,这里是 http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd"> <bean class="lujing.sample.bean.TargetBean" /> </beans>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean class="lujing.sample.bean.TargetBean" /> </beans>
为了不这样的网络请求,jaxp提供了EntityResolver方法,spring就是经过实现该接口,实现了从classpath中加载对应的文件。
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 { //建立 BeanDefinitionDocumentReader 对象 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //记录调用BeanDefinitionDocumentReader以前已经解析完成的 Bean 数量 //getRegistry返回的是咱们建立的 DefaultListableBeanFactory int countBefore = getRegistry().getBeanDefinitionCount(); //解析 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //如今的Bean数量减去以前的数量,就是本次加载的Bean数量 return getRegistry().getBeanDefinitionCount() - countBefore; }
建立BeanDefinitionDocumentReader就是根据设定的class实例化,默认实现是org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass)); }
具体的BeanDefinitionDocumentReader经过registerBeanDefinitions方法完成。
建立XmlReaderContext主要是把XmlBeanDefinitionReader中设置的NamespaceHandlerResolver传递过去,在解析的时候须要用到。这也是设计模式中“门面模式”的体现。真正的逻辑都是委托给别的核心组件来完成。
public XmlReaderContext createReaderContext(Resource resource) { return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, getNamespaceHandlerResolver()); }
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { //设置解析上下文 this.readerContext = readerContext; logger.debug("Loading bean definitions"); //获取根节点 Element root = doc.getDocumentElement(); //调用真正的解析方法 doRegisterBeanDefinitions(root); }
下面是真正的解析核心方法了,方法仍旧是一个模版方法,对最外层的beans节点,作了过滤
解析代码的时候都用到了 delegate.isDefaultNamespace(element)方法,该方法的用处就是判断元素节点是否是 beans 命名空间下的 节点,或者是 自定义节点如 <tx:annotation-driver> 。spring判断的依据就是节点的namespace是否是固定的 http://www.springframework.org/schema/beans,spring在xml解析的时候,设置了 namespaceAware参数为true,每一个节点均可以取到 namespace。
protected void doRegisterBeanDefinitions(Element root) { // 解析一个<beans/>节点,任何嵌套的beans标签都会从新调用一次这个方法 //每一个beans节点都有一个BeanDefinitionParserDelegate,同时再实例化的时候指定 parent //这里主要考虑 传播和保存 benas节点中 default-*(default-init-method等)值 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); //profile机制只对标准beans定义的生效,对于自定义标签订义的beans不生效 if (this.delegate.isDefaultNamespace(root)) { //读取profile属性值,用于区分如生产、测试等 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); //未指定profile默认bean都是生效的 if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); //若是指定了 profiles 且 不符合 environment定义的 激活的 profiles ,那么直接忽略 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } } //扩展点方法,默认实现为空 preProcessXml(root); //利用BeanDefinitionParserDelegate真正的解析节点 parseBeanDefinitions(root, this.delegate); //扩展点方法,默认实现为空 postProcessXml(root); this.delegate = parent; }
里面使用了BeanDefinitionParserDelegate委派处理:
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)) { //默认节点 parseDefaultElement(ele, delegate); } else { //自定义标签解析,如常见的 <tx:annotation-driven/> delegate.parseCustomElement(ele); } } } } //是自定义节点,调用自定义节点的解析方式,这里的考虑点仍是 自定义的 beans 标签,好比 <coustom:beans/> else { delegate.parseCustomElement(root); } }
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { //import节点解析 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } //alias节点解析 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } //最复杂的 bean 节点解析 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } //嵌套的beans标签解析,又回到了顶层解析方法 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { doRegisterBeanDefinitions(ele); } }
import标签其实就是循环解析xml文件,这里就不贴代码了;alias标签其实就是注册别名,也很是简单。
spring 的 自定义标签机制是 spring的扩展的基础,众多功能如context,webmvc,transaction都使用自定义标签,方便用户再极简配置就能享受强大的功能。如 bean扫描<context:component-scan base-package=""/>,事务的<tx:annotation-driven/>等
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); } public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { //获取命名空间 String namespaceUri = getNamespaceURI(ele); //利用命名空间获得一个自定义解析器 NamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } //自定义解析器的 parse 方法 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
从上面的代码能够看到,自定义解析大概分为3步:
1. 获取自定义标签的 namespaceUri ,用的是 node.getNamespaceURI(); ,以<tx:annotation-driven/>为例就是: http://www.springframework.org/schema/tx
xmlns:tx="http://www.springframework.org/schema/tx"
2. 利用 NamespaceHandlerResolver 的 resolve 方法实例化一个 NamespaceHandler
public interface NamespaceHandlerResolver { /** * 根据namespaceUri实例化一个NamespaceHandler对象 */ NamespaceHandler resolve(String namespaceUri); }
这里使用的是 DefaultNamespaceHandlerResolver 实现类:
public NamespaceHandler resolve(String namespaceUri) { //加载 namespaceUri=namespaceHandler 对应表 Map<String, Object> handlerMappings = getHandlerMappings(); Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } // 若是是NamespaceHandler对象就直接返回。这里对象的判断是,前面若是有该标签出现过,这里直接缓存对象了 else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } //NamespaceHandler的完整类路径处理 else { String className = (String) handlerOrClassName; try { Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); //类型检查 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); } NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); //init方法,注意这个方法很重要,是用户扩展点中很重要的一点 namespaceHandler.init(); //覆盖原来的,避免屡次实例化 handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } catch (ClassNotFoundException ex) { throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", ex); } catch (LinkageError err) { throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", err); } } }
能够看到 getHandlerMappings()方法读取了全部的 META-INF/spring.handlers" 文件。仍是以 tx为例,在spring-tx.jar 能够看到 META-INF/spring.handlers 文件的内容:
http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
在上面的代码中咱们能够看到,init()方法是生命周期方法,用于初始化。
public class TxNamespaceHandler extends NamespaceHandlerSupport { static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager"; static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager"; static String getTransactionManagerName(Element element) { return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ? element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME); } @Override public void init() { registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser()); registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser()); } }
init()方法基本上是固定的写法,就是为每一个标签订义一个 BeanDefinitionParser
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.java
/** * Process the given bean element, parsing the bean definition * and registering it with the registry. */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { //利用delegate获取一个BeanDefinitionHolder对象,包含了beanName,alias, BeanDefinition BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { //装饰BeanDefinitionHolder bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 注册到容器中,这里就是 DefaultListableBeanFactory BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // 发送事件通知 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.java
/** * 解析<bean>节点,若是解析错误返回null.错误被通知到 * org.springframework.beans.factory.parsing.ProblemReporter */ public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null); }
简单的作了一个适配:
/** * 解析<bean>节点,若是解析错误返回null.错误被通知到 * org.springframework.beans.factory.parsing.ProblemReporter */ public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { //读取id属性 String id = ele.getAttribute(ID_ATTRIBUTE); //读取name属性 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); //处理name属性为别名 List<String> aliases = new ArrayList<String>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } //默认是beanName为id属性,没有id属性使用第一个beanName做为beanName String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { //校验beanName的惟一性,在同一个Beans标签下面不能重复 checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { //若是没有指定beanName使用,使用spring规则生成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); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) { //出错的时候 error 要用到 this.parseState.push(new BeanEntry(beanName)); //class属性 String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } try { //parent属性 String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } //自定义的bean标签返回的是 GenericBeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent); //解析各类属性 lazy-init,init-method 等 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); //description节点直接读 bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); //解析meta parseMetaElements(ele, bd); //解析lookup-method parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); //解析replaced-method parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); //解析构造函数参数 parseConstructorArgElements(ele, bd); //解析property parsePropertyElements(ele, bd); //解析qualifier parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); 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; }
终于看到了对全部属性元素的解析,其余都是一些相对简单的解析,最难的就是property节点的解析:
public void parsePropertyElements(Element beanEle, BeanDefinition bd) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { //遍历子节点 //isCandidateElement: //(node instanceof Element && (isDefaultNamespace(node) || !isDefaultNamespace(node.getParentNode()))); //标签名称是property //调用parsePropertyElement解析 Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { parsePropertyElement((Element) node, bd); } } }