An overview of

The previous section introduced the configuration system in SpringBoot, and the next section will introduce the most basic and core AutoConfiguration mechanism of SpringBoot. With autoloading, we can quickly build a Web project with zero configuration,

To see how SpringBoot helps us implement autowiring, let’s start with the @SpringBootApplication annotation:

Automatic assembly process

@springBootApplication starts class annotations

Each SpringBoot project has a boot class, which is located in the root directory of the code and is typically named XXXApplication, to which the @SpringBootApplication annotation is added. @ SpringBootApplication annotations in the spring – the boot – autoconfigure engineering org. Springframework. Boot. The autoconfigure package, are defined as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    @AliasFor(annotation = EnableAutoConfiguration.class)Class<? >[] exclude()default {};
 
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
 
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
 
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")Class<? >[] scanBasePackageClasses()default {};
}
Copy the code

The @SpringBootApplication annotation is, by definition, a composite annotation. It is made up of three annotations: @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan

Classes and class names that do not need auto-assembly can be configured using the Exclude and excludeName attributes, as well as the scanBasePackages and scanBasePackageClasses attributes to configure the package paths and class paths that need to be scanned. Let’s take a look at each of the three notes:

@SpringBootConfiguration

The @springBootConfiguration annotation is relatively simple and is defined as follows:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor( annotation = Configuration.class )
    boolean proxyBeanMethods(a) default true;
}
Copy the code

By definition, @SpringBootConfiguration is equivalent to the @Configuration annotation, which is commonly used to identify the class as a JavaConfig Configuration class

@ComponentScan

@ComponentScan annotations are not new annotations introduced by Spring Boot, but are part of Spring container management. The @ComponentScan annotation scans all the classes that need to be injected under the package labeled by an annotation such as @Component, and loads the associated Bean definitions into the container in bulk.

@enableAutoConfiguration Automatically configures annotations

EnableAutoConfiguration @enableAutoConfiguration @enableAutoConfiguration @enableAutoConfiguration @enableAutoConfiguration @enableAutoConfiguration @enableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<? >[] exclude()default {};

   String[] excludeName() default {};

}
Copy the code

It is a combination of annotation, it is by the @ AutoConfigurationPackage and @ Import (AutoConfigurationPackages. The Registrar. Class) combined into, respectively, to introduce below:

@ AutoConfigurationPackage annotations

The @autoConfigurationPackage annotation is defined as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}
Copy the code

The purpose of this annotation is to manage the package to which the annotation is added as a package path for autowiring. In the definition of the @AutoConfigurationPackage annotation, we found an @import annotation, which is provided by Spring to instantiate and add a class to the Spring IoC container. So we want to know the @ Import (AutoConfigurationPackages. The Registrar. Class) what do you need to understand the Registrar which contains the methods in this class.

static class Registrar implements ImportBeanDefinitionRegistrar.DeterminableImports {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      register(registry, new PackageImport(metadata).getPackageName());
   }

   @Override
   public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(newPackageImport(metadata)); }}Copy the code

The Registrar class has two methods: determineImports and registerBeanDefinitions

New PackageImport(metadata).getPackagename () returns the package name of the class where the @SpringBootApplication annotation is located

@ Import (AutoConfigurationImportSelecor. Class) automatically Import the configuration file selector

Then we return to @ EnableAutoConfiguration annotation of @ Import (AutoConfigurationImportSelecor. Class),

An important method under this class is the selectImports method, and Spring loads all the classes returned by this method into the IOC container. So the main purpose of this class is to choose which classes Spring loads into the IOC container.

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if(! isEnabled(annotationMetadata)) {return NO_IMPORTS;
   }
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
         annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
		if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
  
  	// Get the configurations collection
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

Copy the code

This code is the core of the collection is obtained by getCandidateConfigurations configurations and filtering. GetCandidateConfigurations method as follows:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   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;
}
Copy the code

. Here we focus on the next SpringFactoriesLoader loadFactoryNames () method, this method will be from the meta-inf/spring. Factories files to find the automatic configuration of class, here, have to mention the JDK SPI mechanism, There is a great deal of similarity between the SpringFactoriesLoader class name and the meta-INF/Spring.Factories file directory.

Java SPI mechanism and SpringFactoriesLoader

To understand the SpringFactoriesLoader class, we first need to understand the Service Provider Interface (SPI) mechanism in the JDK. Simply put, the SPI mechanism is a mechanism for finding service implementations for interfaces, similar to Spring’s IOC idea, but by moving the control of autofassembly outside the program, it can avoid the use of hard coding in the program, which is especially important in modular design. Using the Java SPI mechanism requires the following steps:

  • After the service provider provides an implementation of the interface, create a file named interface full path name in the META-INF/services directory of the JAR package. The file contains the full path name of the implementation class
  • When an external program assemes the JAR, it can find the implementation class name from the configuration file in the META-INF/services/ directory and load the instantiation to complete the module injection

The SpringFactoriesLoader is similar to this SPI mechanism, except that the files named after the service interface are stored in the meta-INF/Spring. factories folder with a key defined as EnableAutoConfiguration. The SpringFactoriesLoader looks for all configuration files in the Meta-INF/Spring. factories folder, The configuration item whose Key is EnableAutoConfiguration is instantiated as a configuration class through reflection and loaded into the container.

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   MultiValueMap<String, String> result = cache.get(classLoader);
   if(result ! =null) {
      return result;
   }

   try{ 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()) { String factoryTypeName = ((String) entry.getKey()).trim();for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               result.add(factoryTypeName, factoryImplementationName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex); }}Copy the code

This is the basic process and principle of automatic assembly in SpringBoot based on the @SpringBootApplication annotation, but SpringBoot provides us with more than 100 AutoConfiguration classes by default

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
Copy the code

Obviously, it is impossible to introduce all the classes, so in the process of automatic assembly, we need to find out whether there are corresponding configuration classes according to the classpath, and if so, we need to judge according to the conditions to decide whether to assemble. Here I’m going to introduce another set of @conditionalon annotations that SpringBoot often uses.

ConditionalOn ConditionalOn

When building a Spring application, we sometimes want to load a bean into the context of the application only when certain conditions are met. In Spring 4.0, we can do this with the @Conditional annotation. SpringBoot refines the @conditionalon annotation with a series of @conditionalon conditionals:

ConditionalOnProperty: ConditionalOnBean is instantiated only if the supplied property is true. ConditionalOnBean: ConditionalOnBean is instantiated only if an object exists in the current context. ConditionalOnMissingBean is instantiated only if the Class is on the classpath: ConditionalOnExpression is instantiated only if the expression is true: ConditionalOnMissingBean Only in the current context does not exist when an object is instantiated Bean @ ConditionalOnMissingClass: Only when a Class in the Class path does not exist to instantiate Bean @ ConditionalOnNotWebApplication: only when not Web application will instantiate a Bean

Because the @conditionalon series is so full of conditionals, we have no intention of expanding on all of these components. In fact, these annotations work in much the same way, and we only need to look at one of them to see how it works. ConditionalOnClass, which is defined as follows:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interfaceConditionalOnClass { Class<? >[] value()default {};
  String[] name() default {};
}
Copy the code

As you can see, the @conditionalonClass annotation itself has two attributes, a Class value and a String name, so you can use it either way. ConditionalOnClass also includes an @Conditional(onclasscondition.class) annotation. ConditionalOnClass is included in the OnClassCondition class.

OnClassCondition is a subclass of SpringBootCondition, which in turn implements the Condition interface. The Condition interface has only one matches method, as follows:

@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String classOrMethodName = getClassOrMethodName(metadata);
        try {
            ConditionOutcome outcome = getMatchOutcome(context, metadata);
            logOutcome(classOrMethodName, outcome);
            recordEvaluation(context, classOrMethodName, outcome);
            return outcome.isMatch();
        }
        // omit other methods
}
Copy the code

The getClassOrMethodName method here gets the name of the class or method with the @conditionAlonClass annotation added, while the getMatchOutcome method gets the matching output. We see that the getMatchOutcome method is actually an abstract method that needs to be implemented by various subclasses of SpringBootCondition, in this case the OnClassCondition class. To understand OnClassCondition, we need to understand that in Spring Boot, @ ConditionalOnClass or @ ConditionalOnMissingClass annotations are the conditions of the corresponding class OnClassCondition, So in the getMatchOutcome of the OnClassCondition we’re going to deal with both cases. Here we picked the code to handle the @conditionalonclass annotation. The core logic is as follows:

ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if(onClasses ! =null) {
            List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader);
            if(! missing.isEmpty()) {return ConditionOutcome
                        .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
                                .didNotFind("required class"."required classes")
                                .items(Style.QUOTE, missing));
            }
            matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
                    .found("required class"."required classes").items(Style.QUOTE, getMatches(onClasses, MatchType.PRESENT, classLoader));
}
Copy the code

There are two methods worth noting here, the getCandidates method and the getMatches method. The name attribute and value attribute of ConditionalOnClass are obtained by the getCandidates method. These values are then matched using the getMatches method to get the classes specified by these attributes that do not exist in the classloader. If a class is found that should exist in the classloader but does not, a Condition is returned that fails to match. On the other hand, ConditionOutcome is returned if the classloader contains the matching class.

summary

At this point, the whole process of SpringBoot automatic configuration and the basic principle have been described, a lot of content, the whole process is summarized as follows:

Program source code

Github:github.com/dragon8844/…

One last word

If this article is helpful to you, or inspired, help pay attention to it, your support is the biggest motivation I insist on writing, thank you for your support.

In addition, pay attention to the public number: black lighthouse, focus on Java back-end technology sharing, covering Spring, Spring the Boot, SpringCloud, Docker, Kubernetes middleware technology, etc.