/* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot; import java.io.IOException; import java.util.HashSet; import java.util.Set; import groovy.lang.Closure; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter; import org.springframework.core.type.filter.TypeFilter; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** * Loads bean definitions from underlying sources, including XML and JavaConfig. Acts as a * simple facade over {@link AnnotatedBeanDefinitionReader}, * {@link XmlBeanDefinitionReader} and {@link ClassPathBeanDefinitionScanner}. See * {@link SpringApplication} for the types of sources that are supported. * * @author Phillip Webb * @see #setBeanNameGenerator(BeanNameGenerator) */ class BeanDefinitionLoader { private final Object[] sources; private final AnnotatedBeanDefinitionReader annotatedReader; private final XmlBeanDefinitionReader xmlReader; private BeanDefinitionReader groovyReader; private final ClassPathBeanDefinitionScanner scanner; private ResourceLoader resourceLoader; /** * Create a new {@link BeanDefinitionLoader} that will load beans into the specified * {@link BeanDefinitionRegistry}. * @param registry the bean definition registry that will contain the loaded beans * @param sources the bean sources */ BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) { Assert.notNull(registry, "Registry must not be null"); Assert.notEmpty(sources, "Sources must not be empty"); this.sources = sources; this.annotatedReader = new AnnotatedBeanDefinitionReader(registry); this.xmlReader = new XmlBeanDefinitionReader(registry); if (isGroovyPresent()) { this.groovyReader = new GroovyBeanDefinitionReader(registry); } this.scanner = new ClassPathBeanDefinitionScanner(registry); this.scanner.addExcludeFilter(new ClassExcludeFilter(sources)); } /** * Set the bean name generator to be used by the underlying readers and scanner. * @param beanNameGenerator the bean name generator */ public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { this.annotatedReader.setBeanNameGenerator(beanNameGenerator); this.xmlReader.setBeanNameGenerator(beanNameGenerator); this.scanner.setBeanNameGenerator(beanNameGenerator); } /** * Set the resource loader to be used by the underlying readers and scanner. * @param resourceLoader the resource loader */ public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; this.xmlReader.setResourceLoader(resourceLoader); this.scanner.setResourceLoader(resourceLoader); } /** * Set the environment to be used by the underlying readers and scanner. * @param environment the environment */ public void setEnvironment(ConfigurableEnvironment environment) { this.annotatedReader.setEnvironment(environment); this.xmlReader.setEnvironment(environment); this.scanner.setEnvironment(environment); } /** * Load the sources into the reader. * @return the number of loaded beans */ public int load() { int count = 0; for (Object source : this.sources) { count += load(source); } return count; } private int load(Object source) { Assert.notNull(source, "Source must not be null"); if (source instanceof Class<?>) { return load((Class<?>) source); } if (source instanceof Resource) { return load((Resource) source); } if (source instanceof Package) { return load((Package) source); } if (source instanceof CharSequence) { return load((CharSequence) source); } throw new IllegalArgumentException("Invalid source type " + source.getClass()); } private int load(Class<?> source) { if (isGroovyPresent()) { // Any GroovyLoaders added in beans{} DSL can contribute beans here if (GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); load(loader); } } if (isComponent(source)) { this.annotatedReader.register(source); return 1; } return 0; } private int load(GroovyBeanDefinitionSource source) { int before = this.xmlReader.getRegistry().getBeanDefinitionCount(); ((GroovyBeanDefinitionReader) this.groovyReader).beans(source.getBeans()); int after = this.xmlReader.getRegistry().getBeanDefinitionCount(); return after - before; } private int load(Resource source) { if (source.getFilename().endsWith(".groovy")) { if (this.groovyReader == null) { throw new BeanDefinitionStoreException( "Cannot load Groovy beans without Groovy on classpath"); } return this.groovyReader.loadBeanDefinitions(source); } return this.xmlReader.loadBeanDefinitions(source); } private int load(Package source) { return this.scanner.scan(source.getName()); } private int load(CharSequence source) { String resolvedSource = this.xmlReader.getEnvironment() .resolvePlaceholders(source.toString()); // Attempt as a Class try { return load(ClassUtils.forName(resolvedSource, null)); } catch (IllegalArgumentException ex) { // swallow exception and continue } catch (ClassNotFoundException ex) { // swallow exception and continue } // Attempt as resources Resource[] resources = findResources(resolvedSource); int loadCount = 0; boolean atLeastOneResourceExists = false; for (Resource resource : resources) { if (isLoadCandidate(resource)) { atLeastOneResourceExists = true; loadCount += load(resource); } } if (atLeastOneResourceExists) { return loadCount; } // Attempt as package Package packageResource = findPackage(resolvedSource); if (packageResource != null) { return load(packageResource); } throw new IllegalArgumentException("Invalid source '" + resolvedSource + "'"); } private boolean isGroovyPresent() { return ClassUtils.isPresent("groovy.lang.MetaClass", null); } private Resource[] findResources(String source) { ResourceLoader loader = (this.resourceLoader != null ? this.resourceLoader : new PathMatchingResourcePatternResolver()); try { if (loader instanceof ResourcePatternResolver) { return ((ResourcePatternResolver) loader).getResources(source); } return new Resource[] { loader.getResource(source) }; } catch (IOException ex) { throw new IllegalStateException("Error reading source '" + source + "'"); } } private boolean isLoadCandidate(Resource resource) { if (resource == null || !resource.exists()) { return false; } if (resource instanceof ClassPathResource) { // A simple package without a '.' may accidentally get loaded as an XML // document if we're not careful. The result of getInputStream() will be // a file list of the package content. We double check here that it's not // actually a package. String path = ((ClassPathResource) resource).getPath(); if (path.indexOf(".") == -1) { try { return Package.getPackage(path) == null; } catch (Exception ex) { // Ignore } } } return true; } private Package findPackage(CharSequence source) { Package pkg = Package.getPackage(source.toString()); if (pkg != null) { return pkg; } try { // Attempt to find a class in this package ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver( getClass().getClassLoader()); Resource[] resources = resolver.getResources( ClassUtils.convertClassNameToResourcePath(source.toString()) + "/*.class"); for (Resource resource : resources) { String className = StringUtils .stripFilenameExtension(resource.getFilename()); load(Class.forName(source.toString() + "." + className)); break; } } catch (Exception ex) { // swallow exception and continue } return Package.getPackage(source.toString()); } private boolean isComponent(Class<?> type) { // This has to be a bit of a guess. The only way to be sure that this type is // eligible is to make a bean definition out of it and try to instantiate it. if (AnnotationUtils.findAnnotation(type, Component.class) != null) { return true; } // Nested anonymous classes are not eligible for registration, nor are groovy // closures if (type.getName().matches(".*\\$_.*closure.*") || type.isAnonymousClass() || type.getConstructors() == null || type.getConstructors().length == 0) { return false; } return true; } /** * Simple {@link TypeFilter} used to ensure that specified {@link Class} sources are * not accidentally re-added during scanning. */ private static class ClassExcludeFilter extends AbstractTypeHierarchyTraversingFilter { private final Set<String> classNames = new HashSet<>(); ClassExcludeFilter(Object... sources) { super(false, false); for (Object source : sources) { if (source instanceof Class<?>) { this.classNames.add(((Class<?>) source).getName()); } } } @Override protected boolean matchClassName(String className) { return this.classNames.contains(className); } } /** * Source for Bean definitions defined in Groovy. */ @FunctionalInterface protected interface GroovyBeanDefinitionSource { Closure<?> getBeans(); } }