“This is the 12th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

SpringBoot does not scan the contents of the submodule when the package is scanned, making it impossible for beans in our submodule to be injected into the Spring container. SpringBoot provides a spring.factories file that lets you easily inject submodule beans into your Spring container. In this article we’ll explore how spring.factories can instantiate beans across modules.

1. Introduction

We talked about building our own starter in the previous article, and spring.factories play an important role. We use spring.factories to inject beans from the starer project into the Spring container of the Web module. In this article, we’ll take a look at the spring.Factories file, the deeper stuff, and how we instantiate beans with it.

2. The configuration

Spring. factories files are typically configured in the SRC /main/resources/ meta-INF/directory.

You can create a meta-inf folder under the resources directory of your IDEA project and then create a Spring. factories file. As shown in the figure below.

The content of the Spring. factories file is the interface corresponding to its implementation class, and there can be multiple implementation classes

The file content must be of KV type, i.e., properties type

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zhj.config.AutoConfiguration
Copy the code

If an interface has multiple implementations, the configuration is as follows

org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
Copy the code

Principle 3.

The SpringFactoriesLoader class is defined in spring-core, which is the class that makes the Spring.Factories file work. The SpringFactoriesLoader class retrieves the meta-INF/Spring.Factories file and instantiates the implementation with the specified interface. There are two external methods defined in this class:

  • LoadFactories retrieves an instance of its implementation class based on the given interface class. This method returns a list of objects
  • LoadFactoryNames loads the fully qualified class names of the classpath based on the given type. This method returns a list of fully qualified class names.

The source code is as follows:

Public final class SpringFactoriesLoader {public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; / / cache the private static final Map < this MultiValueMap < String, the String > > cache = new ConcurrentReferenceHashMap < > (); Private SpringFactoriesLoader() {} public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) { Assert.notNull(factoryType, "'factoryType' must not be null"); ClassLoader classLoaderToUse = ClassLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } / / load the fully qualified name of a class a List < String > factoryImplementationNames = loadFactoryNames (factoryType classLoaderToUse); if (logger.isTraceEnabled()) { logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames); } List<T> result = new ArrayList<>(factoryImplementationNames.size()); for (String factoryImplementationName : FactoryImplementationNames) {/ / instantiate the beans and Bean to be included in the collection to the List the result. The add (instantiateFactory (factoryImplementationName, factoryType, classLoaderToUse)); } AnnotationAwareOrderComparator.sort(result); return result; } public static List<String> loadFactoryNames(Class<? > factoryType, @nullable ClassLoader ClassLoader) {String factoryTypeName = factoryType.getName(); Value return loadSpringFactories(classLoader).getorDefault (factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@nullable ClassLoader ClassLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result ! = null) { return result; } Try {// Get meta-INF /spring.factories Enumeration<URL> urls = (classLoader! = null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); // Factories while (urls.hasmoreelements ()) {URL URL = urls.nextelement (); // Factories while (urls.hasmoreelements ()) {URL = urls.nextelement (); UrlResource resource = new UrlResource(url); // Load the key value in meta-INF /spring.factories as the Prpperties object Properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<? ,? > Entry: properties.entryset ()) {String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.com maDelimitedListToStringArray ((String) entry. The getValue ())) {/ / factoryTypeName as the key, the implementation class for the value in the map collections result.add(factoryTypeName, factoryImplementationName.trim()); }}} // add cache cache. Put (classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } // Instantiate Bean objects via reflection @SuppressWarnings("unchecked") private static <T> T instantiateFactory(String) factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) { try { Class<? > factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader); if (! factoryType.isAssignableFrom(factoryImplementationClass)) { throw new IllegalArgumentException( "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]"); } return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance(); } catch (Throwable ex) { throw new IllegalArgumentException( "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]", ex); }}}Copy the code

4. To summarize

Spring instantiates the Bean process through the SpringFactoriesLoader

  • Gets the corresponding class loader of the SpringFactoriesLoader
  • Check the cache to see if the contents of meta-INF/Spring.Factories in the CLASspath path of all jars have been read from the cache
  • If the cache already exists, instantiate the Bean by reflection based on the fully qualified class name configured in the/spring.Factories file
  • If there are no values in the cache, the meta-INF/Spring.factories file in all jars is scanned and read into the cache, and the list of configurations is returned
  • The Bean is then instantiated by reflection from this list of fully qualified class names