org.springframework.boot.BeanDefinitionLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.boot.BeanDefinitionLoader.java

Source

/*
 * Copyright 2012-2019 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
 *
 *      https://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.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
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
     */
    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
     */
    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
     */
    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
     */
    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() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
            // Any GroovyLoaders added in beans{} DSL can contribute beans here
            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 | 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 (MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class)) {
            return true;
        }
        // Nested anonymous classes are not eligible for registration, nor are groovy
        // closures
        return !type.getName().matches(".*\\$_.*closure.*") && !type.isAnonymousClass()
                && type.getConstructors() != null && type.getConstructors().length != 0;
    }

    /**
     * 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();

    }

}