For advanced needs, consider using a {@link DefaultListableBeanFactory} with an {@link XmlBeanDefinitionReader}. The latter allows for reading from multiple XML resources and is highly configurable in its actual XML parsing behavior.
publicintloadBeanDefinitions(EncodedResource encodedResource)throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); }
// 检测循环import Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { thrownew 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()); } // 根据EncodedResource中的InputStream以及编码,创建InputSource,后续进行xml解析 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); }finally { inputStream.close(); } }catch (IOException ex) { thrownew BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); }finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
protectedintdoLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); // 解析Document int count = registerBeanDefinitions(doc, resource); // 根据Document解析和注册BeanDefinition if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; }catch (BeanDefinitionStoreException ex) { throw ex; }catch (SAXParseException ex) { thrownew XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); }catch (SAXException ex) { thrownew XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); }catch (ParserConfigurationException ex) { thrownew BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); }catch (IOException ex) { thrownew BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); }catch (Throwable ex) { thrownew BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean){ String id = ele.getAttribute(ID_ATTRIBUTE); // id String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // name
List<String> aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); }
String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isTraceEnabled()) { logger.trace("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } }
if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); }
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); }else { beanName = this.readerContext.generateBeanName(beanDefinition); // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isTraceEnabled()) { logger.trace("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } }catch (Exception ex) { error(ex.getMessage(), ele); returnnull; } } String[] aliasesArray = StringUtils.toStringArray(aliases); returnnew BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } returnnull; }
In general, use this {@code GenericBeanDefinition} class for the purpose of registering user-visible bean definitions (which a post-processor might operate on, potentially even reconfiguring the parent name). Use {@code RootBeanDefinition} / {@code ChildBeanDefinition} where parent/child relationships happen to be pre-determined.
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { // 不再兼容singleton,提示改用scope error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele); }elseif (ele.hasAttribute(SCOPE_ATTRIBUTE)) { // 解析scope属性 bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); }elseif (containingBean != null) { // Take default from containing bean in case of an inner bean definition. bd.setScope(containingBean.getScope()); }
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { // 解析abstract属性 bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); }
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName){ String elementName = (propertyName != null ? "<property> element for property '" + propertyName + "'" : "<constructor-arg> element");
// 除了meta和description之外,只允许有一个直属子节点 // Should only have one child element: ref, value, list, etc. NodeList nl = ele.getChildNodes(); Element subElement = null; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && !nodeNameEquals(node, META_ELEMENT)) { // Child element is what we're looking for. if (subElement != null) { error(elementName + " must not contain more than one sub-element", ele); }else { subElement = (Element) node; } } }
// ref属性、value属性、或者子节点 只能三者选其一 boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); if ((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement != null)) { error(elementName + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); }
if (hasRefAttribute) { // ref String refName = ele.getAttribute(REF_ATTRIBUTE); if (!StringUtils.hasText(refName)) { error(elementName + " contains empty 'ref' attribute", ele); } RuntimeBeanReference ref = new RuntimeBeanReference(refName); ref.setSource(extractSource(ele)); return ref; }elseif (hasValueAttribute) { // value TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); valueHolder.setSource(extractSource(ele)); return valueHolder; }elseif (subElement != null) { // 子节点 return parsePropertySubElement(subElement, bd); }else { // 非法 // Neither child element nor "ref" or "value" attribute found. error(elementName + " must specify a ref or value", ele); returnnull; } }
public BeanDefinitionHolder decorateBeanDefinitionIfRequired( Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd){
// Map of bean definition objects, keyed by bean name privatefinal Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
// List of bean definition names, in registration order privatevolatile List<String> beanDefinitionNames = new ArrayList<>(256);
publicvoidregisterBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null");
// Map from alias to canonical name privatefinal Map<String, String> aliasMap = new ConcurrentHashMap<>(16); publicvoidregisterAlias(String name, String alias){ Assert.hasText(name, "'name' must not be empty"); Assert.hasText(alias, "'alias' must not be empty"); synchronized (this.aliasMap) { if (alias.equals(name)) { this.aliasMap.remove(alias); if (logger.isDebugEnabled()) { logger.debug("Alias definition '" + alias + "' ignored since it points to same name"); } }else { String registeredName = this.aliasMap.get(alias); if (registeredName != null) { if (registeredName.equals(name)) { // An existing alias - no need to re-register return; } if (!allowAliasOverriding()) { thrownew IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } if (logger.isDebugEnabled()) { logger.debug("Overriding alias '" + alias + "' definition for registered name '" + registeredName + "' with new target name '" + name + "'"); } } checkForAliasCircle(name, alias); // 当存在A->B时,若再存在A->C->B则抛出异常 this.aliasMap.put(alias, name); if (logger.isTraceEnabled()) { logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'"); } } } }
protectedvoidimportBeanDefinitionResource(Element ele){ String location = ele.getAttribute(RESOURCE_ATTRIBUTE); // 获取资源路径 if (!StringUtils.hasText(location)) { getReaderContext().error("Resource location must not be empty", ele); return; }
// Resolve system properties: e.g. "${user.dir}" // 替换环境变量 location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<>(4);
// Discover whether the location is an absolute or relative URI boolean absoluteLocation = false; // 先尝试绝对路径获取,如果不行再按相对当前文件的地址获取 try { absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); }catch (URISyntaxException ex) { // cannot convert to an URI, considering the location relative // unless it is the well-known Spring prefix "classpath*:" }
// Absolute or relative? if (absoluteLocation) { try { int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); if (logger.isTraceEnabled()) { logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]"); } }catch (BeanDefinitionStoreException ex) { getReaderContext().error( "Failed to import bean definitions from URL location [" + location + "]", ele, ex); } }else { // No URL -> considering resource location as relative to the current file. 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); } if (logger.isTraceEnabled()) { logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]"); } }catch (IOException ex) { getReaderContext().error("Failed to resolve current resource location", ele, ex); }catch (BeanDefinitionStoreException ex) { getReaderContext().error( "Failed to import bean definitions from relative location [" + location + "]", ele, ex); } } Resource[] actResArray = actualResources.toArray(new Resource[0]); getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele)); }