spring. Wonderful use of factories

phenomenon

When reading the source code related to spring boot, you often see spring In the factories file, the class names related to AutoConfiguration are written, so a question arises: "why write the spring.factories file when the automatically configured class has been annotated with @ Configuration?

Anyone who has used Spring Boot knows

@The function of ComponentScan annotation is to scan all bean s marked by @ component annotation (or expanded @ component annotation) under the package where the Application class of @ SpringBootApplication is located, and register them in the spring container.

So here comes the question...

In the Spring Boot project, what if the bean you want to be managed by the Spring container is not in the Spring Boot package scanning path?

There are two ways to solve the configuration classes in Spring Boot that cannot be scanned by the default path:

(1) Use the @ Import annotation on the Spring Boot main class
(2) Using spring Factories file

The following is a description of using spring Simple understanding of factories file

Spring Factories, the extension mechanism of Spring Boot

There is a very decoupled extension mechanism in Spring Boot: Spring Factories. This extension mechanism is actually implemented by imitating the SPI extension mechanism in Java.

What is? SPI Mechanism?

The full name of SPI is Service Provider Interface Most developers may not be familiar with this because it is for vendors or plug-ins. In Java util. Serviceloader is described in detail in the documentation.

Simply summarize the idea of java SPI mechanism. Each abstract module in our system often has many different implementation schemes, such as the scheme of log module, xml parsing module, jdbc module and so on. In object-oriented design, we generally recommend interface programming between modules, and there is no hard coding between modules. Once a specific implementation class is involved in the code, it violates the principle of pluggability. If you need to replace an implementation, you need to modify the code. In order to realize that the module assembly can not be dynamically specified in the program, a service discovery mechanism is needed.

java SPI provides such a mechanism: a mechanism to find a service implementation for an interface. Similar to the idea of IOC, it is to move the control of assembly outside the program. This mechanism is particularly important in modular design.

Spring Boot Medium SPI mechanism

There is also a loading mechanism similar to Java SPI in Spring. It's in resources / meta-inf / Spring Configure the implementation class name of the interface in the factories file, and then read these configuration files and instantiate them in the program.

This custom SPI mechanism is the basis for the implementation of Spring Boot Starter.

Spring Factories What is the implementation principle?

The spring core package defines the springfactoryesloader class, which implements the retrieval of meta-inf / spring Factories file, and get the function of the configuration of the specified interface. Two external methods are defined in this class:

loadFactories obtains the instance of its implementation class according to the interface class. This method returns the object list.
loadFactoryNames obtains the name of its interface class according to the interface. This method returns a list of class names.

The key of the above two methods is to get spring. From the specified ClassLoader Factories file and parse it to get the list of class names. The specific code is as follows

public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();

    private SpringFactoriesLoader() {}

    public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryClass, "'factoryClass' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }

        List<T> result = new ArrayList(factoryNames.size());
        Iterator var5 = factoryNames.iterator();

        while(var5.hasNext()) {
            String factoryName = (String)var5.next();
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }

        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

    private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
        try {
            Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
            if (!factoryClass.isAssignableFrom(instanceClass)) {
                throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
            } else {
                return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance();
            }
        } catch (Throwable var4) {
            throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);
        }
    }
}

From the code, we can know that in this method, we will traverse the spring.exe under all jar packages in ClassLoader under the classpath of the whole spring boot project Factories file. In other words, we can configure spring.com in our own jar The factories file will not affect the configuration of other places, nor will it be overwritten by the configuration of others.

Spring Factories stay Spring Boot Application in

Spring can be found in many packages of Spring Boot Next, let's take the Spring Boot autoconfigure package as an example

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration

Combined with the previous content, we can see that spring The factories file can register beans outside the spring boot project package (that is, add dependent beans in the pom file) to the spring container of the spring boot project. Since the @ ComponentScan annotation can only scan beans in the spring boot project package and register them in the spring container, the @ EnableAutoConfiguration annotation is required to register beans outside the project package. And spring The factories file is used to record the bean class names that need to be registered outside the project package.

Keywords: Spring Spring Boot

Added by leeming on Tue, 08 Feb 2022 12:48:13 +0200