Source code analysis of initializer of springboot source code

In the previous article, we introduced how to use the initializer

Let's take a look at the implementation principle of the initializer

First, we need to create a springboot project, and then start to trace the source code

package com.jun.lee;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
	public static void main(String[] args) {
		SpringApplication.run(App.class,args);
	}
}

 

We have entered the run method of SpringApplication. We have done two things here. One is to instantiate the SpringApplication class and call its run method

	/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified sources using default settings and user supplied arguments.
	 * @param primarySources the primary sources to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

 

When we go to the construction method of SpringApplication, we can see a setInitializers method, which calls the getSpringFactoriesInstances method and introduces the class class object of ApplicationContextInitializer. This class is what we need to pay attention to.

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = deduceWebApplicationType();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

 

We enter the getSpringFactoriesInstances method, which is an overloaded method. Let's go further

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

 

In the following methods, it is the key method to implement the initializer

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(
                //Load initializer
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //Instantiate initializer
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
        //Sort initializers
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

 

Let's go into the springfactoryesloader. Loadfactorynames (type, classloader)) method to see the detailed loading process

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        //Query cache
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null)
			return result;
		try {
            //Get the path of the spring.factories file under the jar package
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
            //traversal paths
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
                //Parse each spring.factories file and encapsulate it as properties
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                //kv structure traversing properties
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    //Encapsulate multiple value s into a set structure
					List<String> factoryClassNames = Arrays.asList(
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
					result.addAll((String) entry.getKey(), factoryClassNames);
				}
			}
            //Add to cache
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

 

Next, let's look at the createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); method

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
			Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
        //Traversal class name
		for (String name : names) {
			try {
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass
						.getDeclaredConstructor(parameterTypes);
                        //Instanced object
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException(
						"Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

Finally, execute AnnotationAwareOrderComparator.sort(instances); method to sort

public static void sort(List<?> list) {
		if (list.size() > 1) {
			list.sort(INSTANCE);
		}
	}

 

Loading of this initializer is complete

75 original articles published, 5 praised, 10000 visitors+
Private letter follow

Tags: Spring SpringBoot Java

Posted on Thu, 13 Feb 2020 07:27:24 -0800 by Adastra