SpringBoot auto assembly

  • What is SpringBoot auto assembly
  • How does SpringBoot implement automatic assembly

Note: the source code analysis part is based on spring 5.0.4, and the springboot version is 2.0.0.RELEASE

What is SpringBoot auto assembly

    When using SpringBoot to develop applications, we can build a smooth running engineering code very quickly, which benefits from the automatic assembly ability of SpringBoot. However, for automatic assembly, the Spring Framework has already realized this function. SpringBoot is optimized on this basis, and its mechanism is like SPI. However, most developers may only hear about automatic assembly and think of SpringBoot when they mention automatic assembly, but how does automatic assembly come true? What's the matter of on-demand assembly, but we don't go deep into its reason.

When SpringBoot starts, it will scan the / META-INF/spring.factories file in the externally referenced jar package, and then load it into the Spring container according to the configuration in the file. For external jars, to support SpringBoot, you can package the corresponding starter according to the specification of SpringBoot automatic assembly.

    When using the Spring Framework to build a project that needs to rely on a third-party library, in addition to introducing a third-party jar, you also need to make corresponding configuration, perhaps xml configuration or java configuration. In short, this is troublesome, but if you use SpringBoot, We only need to introduce the corresponding starter and add a small amount of configuration in the configuration file application.properties or application.yml. For example, if redis is used in the project, we can directly introduce the starter of spring boot starter data redis in maven, and then use the third-party components through a small amount of annotation or configuration.

How does SpringBoot implement automatic assembly

annotation

    First of all, the most intuitive annotation for a developer is the automatic assembly annotation @ EnableAutoConfiguration provided by SpringBoot, which declares automatic assembly. However, generally, we do not use this annotation, but use the annotation @ SpringBootApplication, which makes a composite annotation @ EnableAutoConfiguration, @ ComponentScan @The collection of SpringBootConfiguration annotations. The functions of these three annotations are to enable SpringBoot automatic assembly, scan all beans modified by @ Service,@Component,@Controller and other annotations under the package where the startup class is located, and register beans in the context to modify the configuration class.

    @ The EnableAutoConfiguration annotation declares automatic assembly, but we are still confused about how to implement automatic assembly. However, looking carefully at the code of the @ EnableAutoConfiguration annotation, we can find that the annotation @ imports the AutoConfigurationImportSelector class, @The Import annotation is used to initialize the AutoConfigurationImportSelector to the Spring context, so the AutoConfigurationImportSelector is actually an implementation of SpringBoot auto assembly. Let's analyze this class.

AutoConfigurationImportSelector.java

The inheritance relationship of this class is as follows:

The inheritance relationship in the red box is the core of automatic assembly, and its core interface is ImportSelector. AutoConfigurationImportSelector implements this interface and rewrites the selectImports method, which returns a string array of the full path name of the class to be assembled.

The following is the code for the implementation of AutoConfigurationImportSelector#selectImports

    /**
    AnnotationMetadata Annotation metadata
    */
    @Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // 1. Whether the automatic assembly switch is turned on
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		try {
            // 2. Read the metadata of all automatically assembled bean s
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
            // 3. Annotation attributes include and exclude name
			AnnotationAttributes attributes = getAttributes(annotationMetadata);
            // 4. Read all auto assembled bean s in the spring boot autoconfigure package META-INF/spring.factories
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);
            // 5. Weight removal
			configurations = removeDuplicates(configurations);
            // 6. Sort according to @ Order annotation
			configurations = sort(configurations, autoConfigurationMetadata);
            // 7. Exclude class es that do not require automatic assembly
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);
			checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);
            // 8. Filter automatically assembled bean s to achieve on-demand assembly
			configurations = filter(configurations, autoConfigurationMetadata);
			fireAutoConfigurationImportEvents(configurations, exclusions);
			return StringUtils.toStringArray(configurations);
		}
		catch (IOException ex) {
			throw new IllegalStateException(ex);
		}
	}
The first step is whether the automatic assembly switch is on
  1. Interpret the spring.boot.enableautoconfiguration configuration. If it is not configured, the default is true, which means that automatic assembly is enabled by default. Otherwise, if false is displayed, automatic assembly will not be performed, and an empty string array will be returned.
    protected boolean isEnabled(AnnotationMetadata metadata) {
		if (getClass() == AutoConfigurationImportSelector.class) {
			// 1
			return getEnvironment().getProperty(
					EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
					true);
		}
		return true;
	}
The second step is to read all auto assembled bean s
  1. Read the metadata of all auto assembly bean s in META-INF/spring-autoconfigure-metadata.properties
  2. Initialize all auto assembled bean s read into Properties
  3. Load all auto assembled bean s into AutoConfigurationMetadata
	public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			// 1. META-INF/spring-autoconfigure-metadata.properties
			Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path));
			Properties properties = new Properties();
			while (urls.hasMoreElements()) {
				// 2
				properties.putAll(PropertiesLoaderUtils
						.loadProperties(new UrlResource(urls.nextElement())));
			}
			// 3
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException(
					"Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

	static AutoConfigurationMetadata loadMetadata(Properties properties) {
		return new PropertiesAutoConfigurationMetadata(properties);
	}

	...
Step 3: get the exclude and excludeName properties of the @ EnableAutoConfiguration annotation
  1. Get @ EnableAutoConfiguration annotation name
  2. Initialize the exclude and excludeName properties, which are configurations that exclude bean s that do not require automatic assembly
    protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
        // 1
		String name = getAnnotationClass().getName();
        // 2
		AnnotationAttributes attributes = AnnotationAttributes
				.fromMap(metadata.getAnnotationAttributes(name, true));
		Assert.notNull(attributes,
				"No auto-configuration attributes found. Is " + metadata.getClassName()
						+ " annotated with " + ClassUtils.getShortName(name) + "?");
		return attributes;
	}
Step 4: read all the auto assembled bean s in the spring-boot-autoconfigure package META-INF/spring.factories

Take a brief look at the following code. The following code is the process of obtaining the list of auto assembled bean s from the spring-boot-autoconfigure package META-INF/spring.factories.

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
        // 1
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
        // 2
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, 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 {
            // 4
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					List<String> factoryClassNames = Arrays.asList(
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
					result.addAll((String) entry.getKey(), factoryClassNames);
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

The fifth step is to automatically assemble the bean to remove the weight

There's nothing to say about this step. Use Set to remove the duplication.

    protected final <T> List<T> removeDuplicates(List<T> list) {
		return new ArrayList<>(new LinkedHashSet<>(list));
	}

Step 6: sort according to @ Order annotation

There is nothing to say about this step. The automatically assembled beans will be sorted first based on the @ Order annotation

    private List<String> sort(List<String> configurations,
			AutoConfigurationMetadata autoConfigurationMetadata) throws IOException {
		configurations = new AutoConfigurationSorter(getMetadataReaderFactory(),
				autoConfigurationMetadata).getInPriorityOrder(configurations);
		return configurations;
	}

Step 7: exclude class es that do not require automatic assembly according to the exclude and excludeName attributes in step 3

There is nothing to say here. Get a collection of bean s to exclude automatic assembly according to the exclude and excludeName in step 3, and then remove all

    @Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		try {
			...
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);
			checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);
			...
		}
		catch (IOException ex) {
			throw new IllegalStateException(ex);
		}
	}

    protected Set<String> getExclusions(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		Set<String> excluded = new LinkedHashSet<>();
		excluded.addAll(asList(attributes, "exclude"));
		excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
		excluded.addAll(getExcludeAutoConfigurationsProperty());
		return excluded;
	}

Step 8: filter the bean s that do not need to be automatically assembled to achieve on-demand assembly

    In this step, beans that do not need to be automatically assembled will be filtered out. We will find that configurations have become smaller. The filtering process here is more complex. The core logic is basically in OnClassCondition and SpringBootCondition classes. Due to many specific logic, we ignore it. We can see that there are 33 beans that need to be automatically assembled after filtering from 110.

    The core of the filter auto assembly bean here is the @ ConditionalOnXXX annotation. Auto assembly can be realized only when the @ ConditionalOnXXX conditions of the configuration class are met. When implementing a custom starter, we can also use this scheme to realize automatic assembly. Here, take the automatic assembly configured by Redis as an example;

  1. @ConditionalOnClass(RedisOperations.class) this code means to check whether RedisOperations exist. If they exist, they will be loaded, otherwise they will not be loaded
  2. 2 and 3 are the same annotation, which means that the bean configured in parentheses will be loaded only when it does not exist. The purpose of this is that sometimes we will write the configuration ourselves instead of automatically assembling the configuration. The purpose of this is to prevent repeated loading.
@Configuration
// 1 
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	// 2
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(
			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	// 3
	@ConditionalOnMissingBean(StringRedisTemplate.class)
	public StringRedisTemplate stringRedisTemplate(
			RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

    The following describes all the @ ConditionalOnXXX annotations related to automatic assembly bean filtering.

  • @ConditionalOnBean: when there is a specified Bean in the container
  • @ConditionalOnMissingBean: when there is no Bean specified in the container
  • @ConditionalOnSingleCandidate: when there is only one specified Bean in the container, or there are multiple specified preferred beans
  • @ConditionalOnClass: when there is a specified class under the class path
  • @ConditionalOnMissingClass: when there is no specified class in the classpath
  • @ConditionalOnProperty: whether the specified property has the specified value
  • @ConditionalOnResource: whether the classpath has the specified value
  • @ConditionalOnExpression: use the SpEL expression as the judgment condition
  • @Conditional on Java: Based on the Java version as the judgment condition
  • @ConditionalOnJndi: the difference is at the specified position when JNDI exists
  • @Conditionalonnot Web application: the current project is not a Web project
  • @ConditionalOnWebApplication: the current project is a Web project

summary

    SpringBoot starts automatic assembly through the @ EnableAutoConfiguration annotation. The @ SpringBootApplication annotation is a combination of annotations, which contains @ EnableAutoConfiguration. The core of SpringBoot automatic assembly is to implement the ImportSelector interface and rewrite the selectImports method in the AutoConfigurationImportSelector#selectImports method, The general steps of automatic assembly are to scan the / META-INF/spring.factories file in the jar package referenced by the SpringFactoriesLoader scanning department, then perform operations such as de duplication and exclusion, and finally perform on-demand assembly. The implementation of on-demand assembly is based on @ Conditional annotation. For example, the @ ConditionalOnBean annotation indicates that the specified bean or class exists in the container before loading, Otherwise it will be filtered out.

Keywords: Java Spring Spring Boot

Added by quecoder on Fri, 19 Nov 2021 23:25:59 +0200