001/*002 * Copyright 2002-2018 the original author or authors.003 *004 * Licensed under the Apache License, Version 2.0 (the "License");005 * you may not use this file except in compliance with the License.006 * You may obtain a copy of the License at007 *008 * https://www.apache.org/licenses/LICENSE-2.0009 *010 * Unless required by applicable law or agreed to in writing, software011 * distributed under the License is distributed on an "AS IS" BASIS,012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.013 * See the License for the specific language governing permissions and014 * limitations under the License.015 */016017package org.springframework.beans.factory.xml;018019import java.io.IOException;020import java.net.URISyntaxException;021import java.util.LinkedHashSet;022import java.util.Set;023024import org.apache.commons.logging.Log;025import org.apache.commons.logging.LogFactory;026import org.w3c.dom.Document;027import org.w3c.dom.Element;028import org.w3c.dom.Node;029import org.w3c.dom.NodeList;030031import org.springframework.beans.factory.BeanDefinitionStoreException;032import org.springframework.beans.factory.config.BeanDefinitionHolder;033import org.springframework.beans.factory.parsing.BeanComponentDefinition;034import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;035import org.springframework.core.io.Resource;036import org.springframework.core.io.support.ResourcePatternUtils;037import org.springframework.lang.Nullable;038import org.springframework.util.Assert;039import org.springframework.util.ResourceUtils;040import org.springframework.util.StringUtils;041042/**043 * Default implementation of the {@link BeanDefinitionDocumentReader} interface that044 * reads bean definitions according to the "spring-beans" DTD and XSD format045 * (Spring's default XML bean definition format).046 *047 * <p>The structure, elements, and attribute names of the required XML document048 * are hard-coded in this class. (Of course a transform could be run if necessary049 * to produce this format). {@code <beans>} does not need to be the root050 * element of the XML document: this class will parse all bean definition elements051 * in the XML file, regardless of the actual root element.052 *053 * @author Rod Johnson054 * @author Juergen Hoeller055 * @author Rob Harrop056 * @author Erik Wiersma057 * @since 18.12.2003058 */059public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {060061 public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;062063 public static final String NESTED_BEANS_ELEMENT = "beans";064065 public static final String ALIAS_ELEMENT = "alias";066067 public static final String NAME_ATTRIBUTE = "name";068069 public static final String ALIAS_ATTRIBUTE = "alias";070071 public static final String IMPORT_ELEMENT = "import";072073 public static final String RESOURCE_ATTRIBUTE = "resource";074075 public static final String PROFILE_ATTRIBUTE = "profile";076077078 protected final Log logger = LogFactory.getLog(getClass());079080 @Nullable081 private XmlReaderContext readerContext;082083 @Nullable084 private BeanDefinitionParserDelegate delegate;085086087 /**088 * This implementation parses bean definitions according to the "spring-beans" XSD089 * (or DTD, historically).090 * <p>Opens a DOM Document; then initializes the default settings091 * specified at the {@code <beans/>} level; then parses the contained bean definitions.092 */093 @Override094 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {095 this.readerContext = readerContext;096 doRegisterBeanDefinitions(doc.getDocumentElement());097 }098099 /**100 * Return the descriptor for the XML resource that this parser works on.101 */102 protected final XmlReaderContext getReaderContext() {103 Assert.state(this.readerContext != null, "No XmlReaderContext available");104 return this.readerContext;105 }106107 /**108 * Invoke the {@link org.springframework.beans.factory.parsing.SourceExtractor}109 * to pull the source metadata from the supplied {@link Element}.110 */111 @Nullable112 protected Object extractSource(Element ele) {113 return getReaderContext().extractSource(ele);114 }115116117 /**118 * Register each bean definition within the given root {@code <beans/>} element.119 */120 @SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)121 protected void doRegisterBeanDefinitions(Element root) {122 // Any nested <beans> elements will cause recursion in this method. In123 // order to propagate and preserve <beans> default-* attributes correctly,124 // keep track of the current (parent) delegate, which may be null. Create125 // the new (child) delegate with a reference to the parent for fallback purposes,126 // then ultimately reset this.delegate back to its original (parent) reference.127 // this behavior emulates a stack of delegates without actually necessitating one.128 BeanDefinitionParserDelegate parent = this.delegate;129 this.delegate = createDelegate(getReaderContext(), root, parent);130131 if (this.delegate.isDefaultNamespace(root)) {132 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);133 if (StringUtils.hasText(profileSpec)) {134 String[] specifiedProfiles = StringUtils.tokenizeToStringArray(135 profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);136 // We cannot use Profiles.of(...) since profile expressions are not supported137 // in XML config. See SPR-12458 for details.138 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {139 if (logger.isDebugEnabled()) {140 logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +141 "] not matching: " + getReaderContext().getResource());142 }143 return;144 }145 }146 }147148 preProcessXml(root);149 parseBeanDefinitions(root, this.delegate);150 postProcessXml(root);151152 this.delegate = parent;153 }154155 protected BeanDefinitionParserDelegate createDelegate(156 XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {157158 BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);159 delegate.initDefaults(root, parentDelegate);160 return delegate;161 }162163 /**164 * Parse the elements at the root level in the document:165 * "import", "alias", "bean".166 * @param root the DOM root element of the document167 */168 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {169 if (delegate.isDefaultNamespace(root)) {170 NodeList nl = root.getChildNodes();171 for (int i = 0; i < nl.getLength(); i++) {172 Node node = nl.item(i);173 if (node instanceof Element) {174 Element ele = (Element) node;175 if (delegate.isDefaultNamespace(ele)) {176 parseDefaultElement(ele, delegate);177 }178 else {179 delegate.parseCustomElement(ele);180 }181 }182 }183 }184 else {185 delegate.parseCustomElement(root);186 }187 }188189 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {190 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {191 importBeanDefinitionResource(ele);192 }193 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {194 processAliasRegistration(ele);195 }196 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {197 processBeanDefinition(ele, delegate);198 }199 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {200 // recurse201 doRegisterBeanDefinitions(ele);202 }203 }204205 /**206 * Parse an "import" element and load the bean definitions207 * from the given resource into the bean factory.208 */209 protected void importBeanDefinitionResource(Element ele) {210 String location = ele.getAttribute(RESOURCE_ATTRIBUTE);211 if (!StringUtils.hasText(location)) {212 getReaderContext().error("Resource location must not be empty", ele);213 return;214 }215216 // Resolve system properties: e.g. "${user.dir}"217 location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);218219 Set<Resource> actualResources = new LinkedHashSet<>(4);220221 // Discover whether the location is an absolute or relative URI222 boolean absoluteLocation = false;223 try {224 absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();225 }226 catch (URISyntaxException ex) {227 // cannot convert to an URI, considering the location relative228 // unless it is the well-known Spring prefix "classpath*:"229 }230231 // Absolute or relative?232 if (absoluteLocation) {233 try {234 int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);235 if (logger.isTraceEnabled()) {236 logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");237 }238 }239 catch (BeanDefinitionStoreException ex) {240 getReaderContext().error(241 "Failed to import bean definitions from URL location [" + location + "]", ele, ex);242 }243 }244 else {245 // No URL -> considering resource location as relative to the current file.246 try {247 int importCount;248 Resource relativeResource = getReaderContext().getResource().createRelative(location);249 if (relativeResource.exists()) {250 importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);251 actualResources.add(relativeResource);252 }253 else {254 String baseLocation = getReaderContext().getResource().getURL().toString();255 importCount = getReaderContext().getReader().loadBeanDefinitions(256 StringUtils.applyRelativePath(baseLocation, location), actualResources);257 }258 if (logger.isTraceEnabled()) {259 logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");260 }261 }262 catch (IOException ex) {263 getReaderContext().error("Failed to resolve current resource location", ele, ex);264 }265 catch (BeanDefinitionStoreException ex) {266 getReaderContext().error(267 "Failed to import bean definitions from relative location [" + location + "]", ele, ex);268 }269 }270 Resource[] actResArray = actualResources.toArray(new Resource[0]);271 getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));272 }273274 /**275 * Process the given alias element, registering the alias with the registry.276 */277 protected void processAliasRegistration(Element ele) {278 String name = ele.getAttribute(NAME_ATTRIBUTE);279 String alias = ele.getAttribute(ALIAS_ATTRIBUTE);280 boolean valid = true;281 if (!StringUtils.hasText(name)) {282 getReaderContext().error("Name must not be empty", ele);283 valid = false;284 }285 if (!StringUtils.hasText(alias)) {286 getReaderContext().error("Alias must not be empty", ele);287 valid = false;288 }289 if (valid) {290 try {291 getReaderContext().getRegistry().registerAlias(name, alias);292 }293 catch (Exception ex) {294 getReaderContext().error("Failed to register alias '" + alias +295 "' for bean with name '" + name + "'", ele, ex);296 }297