spring. Loading principle of factories and custom environment postprocessor

catalogue

spring. Loading principle of factories

1. Construction method of springapplication

 1.1 SpringApplication#getSpringFactoriesInstances

 1.1.1SpringFactoriesLoader#loadFactoryNames-->loadSpringFactories

1.1.2 createSpringFactoriesInstances

Customize EnvironmentPostProcessor

Question: how to make the customized environment postprocessor take effect???

Reason introduction

terms of settlement

spring. Loading principle of factories

1. Construction method of springapplication

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 = WebApplicationType.deduceFromClasspath();
        
//Used to obtain the specified class instance of ApplicationContextInitializer interface in Spring;
        //The file path in the whole project is meta-inf / spring The content in factories instantiates the corresponding instance class. Before this step, the attribute cache in spring factoriesloader is null
  setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));
//Used to obtain the specified class instance of applicationlister interface in Spring;
        //The file path in the whole project is meta-inf / spring The content in factories instantiates the corresponding instance class. Today, we will focus on this method
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

 1.1 SpringApplication#getSpringFactoriesInstances

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    	//Returns an instance of the specified type (ApplicationListener)
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

//Method of real execution
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    	//Get class loader
		ClassLoader classLoader = getClassLoader();
		// Get the name of the corresponding bean
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    	//Using reflection to generate instanced objects
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
    	//Add to the started listeners
		return instances;
	}

 1.1.1SpringFactoriesLoader#loadFactoryNames-->loadSpringFactories

//ConcurrentMap (a thread safe map set) is used as a cache, an accessibleConstructor is created through the constructor, and isAssignableFrom determines whether it is a subclass (the difference between isAssignableFrom and instanceof keywords)

/**
 * Internal use framework implemented by General Factory loading mechanism. spring internal use
 *
 * springfactoresloader Meta-inf / spring will be loaded Factories of the type given in the factories file,
 * Why can value correspond to more than one factory,
 * spring.factories The file type of is in the format of properties, where the key is the name of a fully qualified interface or abstract class, and the values are separated by commas
 *
 * For example:
 * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
 *
 */
public final class SpringFactoriesLoader {

	/**
	 * Local factory documents,
	 * It can exist in multiple jars, that is, it will scan all jars, and then parse all meta-inf / spring Factories file,
	 * And create its configured factory and instantiate all value s
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


	// Log frame
	private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

	// Define a map, where key is classloader and value is MultiValueMap
	// MultiValueMap integrated from Map
	// ConcurrentReferenceHashMap is integrated from ConcurrentMap, that is, a thread safe map
	private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();


	// Null parameter constructor
	private SpringFactoriesLoader() {
	}

    /**
	 * From meta-inf / spring. Net using the given class loader Factories loads the fully qualified class name of the factory implementation of the given type.
	 * @param factoryType Class object of interface or abstract class
	 *
	 * @param classLoader classLoader Class loader for loading (can be null, if NULL, use the default value)
	 *
	 * @throws IllegalArgumentException If an error occurs while loading the factory name
	 * @see #loadFactories
	 */
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		// Get the fully qualified Class name (org.springframework.context.ApplicationListener) through the Class object
		String factoryTypeName = factoryType.getName();
		// The loadSpringFactories method is to get all the springFactories
		// getOrDefault is the collection of corresponding classes obtained through the key, that is, the right qualified name. Because value is separated by commas and can have multiple values, it is a list
		// getOrDefault returns if it exists, and returns the default value of if it does not exist
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

    // Load all spring factories
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		// First, get it from the cache. According to the classLoader,
		// The cache uses ClassLoader as the key. It is statically modified by final. There is only one copy of the whole application
		MultiValueMap<String, String> result = cache.get(classLoader);
		// If it is null, the certificate has not been loaded. If it is not empty, it is added.
		if (result != null) {
			return result;
		}

		try {
			// Three item expression to judge whether the parameter classLoader is empty. If it is not empty, the incoming classLoader is directly used to obtain meta-inf / spring factories
			// If it is empty, use the classLoader of the system to get meta-inf / spring factories
			// In short, it has strong robustness,
			Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				// Loop through all meta-inf / spring factories
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				// Parse properties
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				// Put all key s into result
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        //factoryTypeName
                        //org.springframework.boot.autoconfigure.EnableAutoConfiguration  						  //factoryImplementationName.trim()
                    //	com.nieyp.ausware.elasticsearch.ElasticsearchStarterAutoConfiguration
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			// Put the loaded into the cache
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
    
	/**
	 * From meta-inf / spring. Under the classpath of each jar package through classLoader Factories loads and parses its key value value, and then creates a factory implementation of its given type
	 *
	 * The returned factories are sorted by AnnotationAwareOrderComparator.
	 * AnnotationAwareOrderComparator It is sorted by the value above the @ Order annotation. The higher the value, the lower the ranking
	 *
	 * If you need to customize the instantiation policy, use the loadFactoryNames method to obtain the names of all registered factories.
	 *
	 * @param  factoryType Class object of interface or abstract class
	 * @param  classLoader Class loader for loading (can be null, if NULL, use the default value)
	 * @throws IllegalArgumentException An IllegalArgumentException exception is thrown if any factory implementation classes cannot be loaded, or if an error occurs when instantiating any factory
	 */
	public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		// First, assert that the Class object of the incoming interface or abstract Class cannot be empty
		Assert.notNull(factoryType, "'factoryType' must not be null");
		// Assign the passed in classLoader to classLoaderToUse
		// Judge whether classLoaderToUse is empty. If it is empty, use the default classLoader of spring factoryesloader
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		// Load all meta-inf / spring Factories and parse it to get its configured factoryNames
        //factoryType is environmentpostprocessor class
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
		}

		List<T> result = new ArrayList<>(factoryImplementationNames.size());
		// Instantiate all configurations through reflection.
		for (String factoryImplementationName : factoryImplementationNames) {
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}

	


	

	// Example chemical plant, according to
	@SuppressWarnings("unchecked")
	private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
		try {
			// Get the Class object through reflection according to the fully qualified Class name
			Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
			// Judge whether the obtained Class object comes from factoryType,
			// The specific point is to judge the spring we configured The permission in factories determines whether the class corresponding to the class name is the corresponding subclass or implementation class
			// such as
			// org.springframework.context.ApplicationContextInitializer=\
			// org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
			// org.springframework.boot.context.ContextIdApplicationContextInitializer,
			// Then he will verify whether configurationwarnings ApplicationContextInitializer and ContextIdApplicationContextInitializer are subclasses of ApplicationContextInitializer
//			The difference between the isAssignableFrom() method and the instanceof keyword is summarized in the following two points:
//			The isAssignableFrom() method is judged from the perspective of class inheritance, and the instanceof keyword is judged from the perspective of instance inheritance.
//			The isAssignableFrom() method determines whether it is the parent class of a class, and the instanceof keyword determines whether it is the child class of a class.
			// If not, it is saved
            //factoryType:interface org.springframework.boot.env.EnvironmentPostProcessor
            //factoryImplementationClass:class com.nieyp.ausware.elasticsearch.core.CustormEnvironmentPostProcessor
            //It should be noted that isAssignableFrom is modified by native and cannot be viewed directly from the implementation
            //public native boolean isAssignableFrom(Class<?> cls);
			if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
				throw new IllegalArgumentException(
						"Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
			}
			// Instantiation through the reflected parametric constructor: if newInstance is used directly, it can only be instantiated through the null parameter constructor.
			// In this way, you can create instances through constructors with different parameters, but no parameters are passed in here, so the default null constructor is called
			return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
		}
		catch (Throwable ex) {
			throw new IllegalArgumentException(
				"Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
				ex);
		}
	}

}
  • The return value of the loadSpringFactories method after walking: it will read all spring Factories and put the corresponding key and ball into the cache. Next time, you can get them directly from the cache (setlisteners ((Collection) getspringfactoryesinstances (applicationlistener. Class)). This method is to get them directly from the cache and get all the implementations of environmentPostProcessor later). Note that no instances are generated at this time

  • Finish loading spring factories (classloader) Getordefault (factoryTypeName, collections. Emptylist()): the required beanName will be filtered out according to the incoming factoryTypeName

1.1.2 createSpringFactoriesInstances

  • Instantiation by reflection (not described here)
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}
  • This completes our spring The process of loading factories Of course, it's not just here to load; In autoconfigurationimportselector, configfileapplicationlistener (described below) will also be called

Customize EnvironmentPostProcessor

public class CustormEnvironmentPostProcessor implements EnvironmentPostProcessor{

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        //What you get is the PropertySource collection. At present, you can also take a specific PropertySource by specifying a specific name
        PropertySource<?> configurationProperties = environment.getPropertySources().get("configurationProperties");
        MutablePropertySources propertySources = environment.getPropertySources();
        for (PropertySource<?> propertySource : propertySources) {
            Object object = propertySource.getProperty("com.name");
            if (ObjectUtils.isNotEmpty(object) || object instanceof String){
                Properties properties = new Properties();
                properties.setProperty("com.name", "update");
                String name = propertySource.getName();
                if (name.equals("configurationProperties")){
                    continue;
                }
                PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource(name, properties);
                propertySources.addLast(propertiesPropertySource);
            }
        }

       /* Properties properties = new Properties();
        properties.setProperty("com.name", "update");
        PropertiesPropertySource propertySource = new PropertiesPropertySource("Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'", properties);
        propertySources.addLast(propertySource);*/
    }
}

Question: how to make the customized environment postprocessor take effect???

  • Whether to annotate the class with @ component or register the bean in the configuration class: you will find that it can't

Reason introduction

  • If the implementation of EnvironmentPostProcessor wants to execute, either create an instance through reflection or create beans through normal process; No matter adding @ component annotation to the class or registering this bean in the configuration class, the normal process is followed, and the calling process of springboot is as follows

    • ......
      
      Configurableenvironment = prepareenvironment (listeners, applicationarguments): read yml or properties and system properties here and put them into the environment. The key steps are as follows:
      • private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
                //Get all the implementations of our EnvironmentPostProcessor
        		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
        		postProcessors.add(this);
        		AnnotationAwareOrderComparator.sort(postProcessors);
                //Traverse and execute our postProcessEnvironment method
        		for (EnvironmentPostProcessor postProcessor : postProcessors) {
        			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
        		}
        	}
    • ......
    • refreshContext(context): the normal process of creating bean s will be followed here. At this moment, the postProcessEnvironment method inside will not be executed

terms of settlement

  • That is to say, when we need loadPostProcessors, we must instantiate our customized implementation. Let's take a look at the process of loadPostProcessors

    • #ConfigFileApplicationListener.loadPostProcessors

List<EnvironmentPostProcessor> loadPostProcessors() {
		return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
	}
  • #SpringFactoriesLoader. Loadfactories (see the previous section for specific analysis, which will not be introduced here)

    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();
    		}
    		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) {
    			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
    		}
    		AnnotationAwareOrderComparator.sort(result);
    		return result;
    	}
  • That is to say, we must register the implementation of our EnvironmentPostProcessor in spring In factories Only in this way can we call EnvironmentPostProcessor Create our implementation bean before the postprocessenvironment method; We can do some operations in the implementation of EnvironmentPostProcessor

  • #The return result of loadFactories#loadFactoryNames is as follows -- > and then the final result will be returned through reflection instantiation

Tags: Java Spring Back-end

Posted by jaddy on Sat, 14 May 2022 23:07:49 +0300