Springboot2.0.6 (attachment) resolves META-INF/spring.factories to obtain the fully qualified name of the corresponding class through the system loading class

Posted by JeremyMorgan on Thu, 17 Oct 2019 09:00:55 +0200

In spring boot, get the META-INF/spring.factories file under all Classpaths through the getspringfactoriesinstances (class < T > type) method, and then find the fully qualified name list of the corresponding class according to the type value. Let me analyze how the getspringfactors instances (class < T > type) method works

Source code analysis

getSpringFactoriesInstances() method

public class SpringApplication {
   private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
   }
    // Get Spring factory
   private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
          Class<?>[] parameterTypes, Object... args) {
        // Get ClassLoader
       ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
       // Use names and ensure unique to protect against duplicates
        // Define the class array, that is, the return value names is the parent node defined in META-INF/spring.factories under the classpath (Figure 2)
       Set<String> names = new LinkedHashSet<>(
         SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // Inner loop initializes the constructor of names to get the instance object (Figure 2)
       List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
         classLoader, args, names);
       AnnotationAwareOrderComparator.sort(instances);
       return instances;
   }
   // Create a Spring factory instance
   private <T> List<T> createSpringFactoriesInstances(Class<T> type,
      Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
      Set<String> names) {
      List<T> instances = new ArrayList<>(names.size());
       // Loop through the values of names
      for (String name : names) {
          try { //(Fig. 3)
             //Load the corresponding Class through the specified classloader to obtain the corresponding Class object
             Class<?> instanceClass = ClassUtils.forName(name, classLoader);
             Assert.isAssignable(type, instanceClass);
             Constructor<?> constructor = instanceClass
               .getDeclaredConstructor(parameterTypes);
             //Create an instance
             T instance = (T) BeanUtils.instantiateClass(constructor, args);
             instances.add(instance);
          } catch (Throwable ex) {
             throw new IllegalArgumentException(
               "Cannot instantiate " + type + " : " + name, ex);
         }
      }
      return instances;
   }
}

The getspringfactoryinstances method obtains the system loading class through the loadFactoryNames method of the springfactorysloader class and gets the fully qualified name of the corresponding class in the meta-inf / spring.factors file under all Classpaths. In combination, execute the createspringfactoryinstances method to traverse the collection and create the instance in a circular way. Then return to the instance object collection.

  • names to get the values of the applicationContextInitializer related loading classes in the META-INF/spring.factories file under all Classpaths
  • Instances list of Spring Factory instances created for traversing names

Take getspringfactors instances (applicationcontextinitializer. Class) as an example to debug

Figure 1-1 and Figure 1-2 mark all fully qualified names corresponding to ApplicationContextInitializer.class in META-INF/spring.factories file under all Classpaths.

(Fig. 1-1)

(Fig. 1-2)

(Figure 2) according to the class name "applicationContextInitializer", obtain the factory class related to applicationContextInitializer in the spring.factories file and initialize it)

 

(Figure 3) initialize and create an instance based on the class name

Enter the loadFactoryNames method of the SpringFactoresLoader class in the getspringfactoryesinstances method of the SpringApplication class

public abstract class SpringFactoriesLoader {
   public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
   private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
    // Loading factory
   public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
      // Get the class name (Figure 4)
      String factoryClassName = factoryClass.getName();
      // Get the name of the factory class to be loaded according to the class name
      return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
   }
    // Scan the loading class by loading the META-INF/spring.factories file under all Classpaths, (Figure 8)
   private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        // If the result set of getting instance from cache is Null, the current cache is empty; cache implements new concurrentreferencehashmap < > ()
      MultiValueMap<String, String> result = cache.get(classLoader);
      if (result != null) {
         return result;
      }
      try {
          // Get the urls in META-INF/spring.factories under all Classpaths (Figure 5)
          // When classLoader is non empty, call getresources method to get
          // When classLoader is empty, call classLoader.getsystemresources method to get
         Enumeration<URL> urls = (classLoader != null ?
               classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
               ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
         result = new LinkedMultiValueMap<>();
          // Loop elements in urls, get elements 
         while (urls.hasMoreElements()) {
             // Get element url address (Figure 6) 
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
             // Parsing files turns files into configuration properties (Figure 6)
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
             // Loop parse and put the result in result
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                // List of class names (Figure 7)
               List<String> factoryClassNames = Arrays.asList(
                     StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
               result.addAll((String) entry.getKey(), factoryClassNames);
            }
         }
          // Result set of cache class loader and file parser
         cache.put(classLoader, result);
         return result;
      }
      catch (IOException ex) {
         throw new IllegalArgumentException("Unable to load factories from location [" +
               FACTORIES_RESOURCE_LOCATION + "]", ex);
      }
   }
}

Take loadFactoryNames(ApplicationContextInitializer.class, classLoader) as an example for debug ging analysis

(Figure 4) get the full class name

(Figure 5) get urls of META-INF/spring.factories file under all Classpaths

(Figure 6) get the specific location and contents of the spring.factories file)(Figure 7) get the list of corresponding classes in the contents of spring.factories file

(Fig. 8)

summary

Load the META-INF/spring.factories file under classpath through classLoader, obtain the file information, parse it into configuration attributes and store it in the result set, and obtain the result set corresponding to type from the result set according to the fully qualified name of type. Loop facilitates the creation of instances from the result set based on fully qualified names. Return after sorting the instance collection

Topics: Programming Spring