The principle of spring-boot startup automatic loading configuration is related

    The principle of spring-boot automatic loading

Overview:

In the spring-boot project, we only need to create a startup class and annotate the @SpringBootApplication annotation to complete the automated configuration. The principle of this is mainly the function of the @SpringBootApplication annotation. Let's analyze this annotation in detail.

 One, SpringBootApplication annotation

@SpringBootApplication comment primary packaging three sub-annotated as follows:
@ SpringBootConfiguration, @ EnableAutoConfiguration, @ ComponentScan

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public @interface SpringBootApplication {
   
   

Among them, @ ComponentScan is used to scan the role of the package, @ SpringBootConfiguration is a wrapper of @Configuration, and @Configuration is a wrapper of @Component, which means that this class is a configuration class.
        
@EnableAutoConfiguration annotation is our main analysis object.

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

There are two main functions of this annotation, one is @AutoConfigurationPackage and the other is @Import (key analysis)

We know that @Import can also specify subclasses implemented by ImportSelector in addition to loading the classes of the specified class. The interface method selectImports, the returned list is the class that this @Import annotation needs to load.

The concrete realization of EnableAutoConfigurationImportSelector.

public String[] selectImports(AnnotationMetadata metadata) {
			try {
				AnnotationAttributes attributes = getAttributes(metadata);
				List<String> configurations = getCandidateConfigurations(metadata,
						attributes); // 加载候选的配置类
				// ....略
				return configurations.toArray(new String[configurations.size()]);
			}
			catch (IOException ex) {
				throw new IllegalStateException(ex);
			}
		}

The selectImports method calls the getCandidateConfigurations method to load candidate classes, and this method actually uses the loadFactoryNames method of SpringFactoriesLoader to load various auto-configuration classes.

		protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
			List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
					getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
			
			return configurations;
		}

The loadFactoryNames method of SpringFactoriesLoader passes in the full class name of an EnableAutoConfiguration class, and then mainly loads the resource file from the META-INF/spring.factories path, traverses, and then finds the value value that matches the full class name key for analysis and processing.

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
			String factoryClassName = factoryClass.getName();
			try {
				Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
						ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); // 加载资源文件
				List<String> result = new ArrayList<String>();
				while (urls.hasMoreElements()) {
					URL url = urls.nextElement();
					Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); // 解析成prop
					String factoryClassNames = properties.getProperty(factoryClassName); // 得到factoryClass的属性
					result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
				}
				return result;
			}
			catch (IOException ex) {
			}
		}

        Below we find the configuration in this resource file from the META-INF in the jar package of spring-boot-autoconfigure.

  org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
        org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
        org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
        org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\

    2. Configuration explanation of some default configuration classes

1. Analysis of DataSourceAutoConfiguration

1) Configuration of annotations on the class

@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })

This annotation packs a total of 4 sub-annotations. @Configuration is an annotation configuration class, @ConditionalOnClass indicates that a DataSource class must exist, and @EnableConfigurationProperties is for loading configuration files.
 The two classes introduced by @Import all use the method of directly specifying class loading. One of the Registrar is used to generate a BeanPostProcessor to realize the purpose of loading the DataSourceInitializer after the bean of the datasource is initialized. The DataSourcePoolMetadataProvidersConfiguration is to provide the configuration of the data source provider class. Such as DBCP, TOMCAT, etc. data source providing classes.

2) Method and subclass configuration

Method 1) The dataSourceInitializer configuration is mainly to initialize a public DataSourceInitializer bean. The main function of this bean is to assign the url
            and password of the configuration class configuration to the created datasource by default.

        @Bean
            @ConditionalOnMissingBean // 表示
            public DataSourceInitializer dataSourceInitializer() {
                return new DataSourceInitializer();
            }

Inner class 2) PooledDataSourceConfiguration This inner class mainly provides classes for several data sources, which is directly provided by @Import.

        @Configuration
            @Conditional(PooledDataSourceCondition.class)
            @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
            @Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class,
                    DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class,
                    DataSourceConfiguration.Generic.class })
            protected static class PooledDataSourceConfiguration {
            }

     This internal class must have the dbcp2 data source class, and the spring.datasource.type must be specified correctly or not provided (matchIfMissing = true). How to create a data source? The final execution of its creation method is DataSourceBuilder.create().....type().build(),

   @ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class) 
            @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp2.BasicDataSource", matchIfMissing = true)
            static class Dbcp2 extends DataSourceConfiguration {
        
                @Bean
                @ConfigurationProperties("spring.datasource.dbcp2")
                public org.apache.commons.dbcp2.BasicDataSource dataSource(
                        DataSourceProperties properties) {
                    return createDataSource(properties,
                            org.apache.commons.dbcp2.BasicDataSource.class);
                }
            }

 Finally, let's list the datasource we created by ourselves, created by DataSourceBuilder, and then use the configuration file to give properties

       @ConfigurationProperties(prefix = "spring.datasource.xxx")
            @Bean(destroyMethod = "close", name = "xxx")
            public DataSource dataSource1() {
                return DataSourceBuilder.create().type(dataSourceType).build();
            }

    3. Source code analysis of Conditional series of annotations

    We use various ConditionalOnClass conditions in the above configuration class . Below we analyze the principle of this type of annotation from a ConditionalOnClass annotation. First of all, the value value of Conditional annotation is an interface that implements Condition. If the implementation of the matches method of this interface returns true, the bean will be loaded, and the annotation of ConditionalOnClass actually wraps Conditional, and the class that implements the Condition interface is OnClassCondition

1. OnClassCondition
            OnClassCondition inherits SpringBootCondition, and SpringBootCondition implements the match method, but in fact matches calls the subclass template method  getMatchOutcome . The method is as follows (note that ConditionOutcome is a po class that wraps a boolean type of mark and information, the match method is true, and noMatch is false)

  public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
            ConditionMessage matchMessage = ConditionMessage.empty();
            MultiValueMap<String, Object> onClasses = getAttributes(metadata,ConditionalOnClass.class); // 得到注解元素
            if (onClasses != null) {
                List<String> missing = getMatchingClasses(onClasses, MatchType.MISSING,context);  // 判断缺少的类
                if (!missing.isEmpty()) {
                    return ConditionOutcome
                            .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
                                    .didNotFind("required class", "required classes")
                                    .items(Style.QUOTE, missing));
                }
            }
            //....
            return ConditionOutcome.match(matchMessage);
        }
private List<String> getMatchingClasses(MultiValueMap<String, Object> attributes,
				MatchType matchType, ConditionContext context) {
			List<String> matches = new LinkedList<String>();
			addAll(matches, attributes.get("value"));
			addAll(matches, attributes.get("name"));
			Iterator<String> iterator = matches.iterator();
			while (iterator.hasNext()) {
				if (!matchType.matches(iterator.next(), context)) {
					iterator.remove(); // matchType传入的枚举是不符合,这里!就是符合的移除,剩余的就是没有提供的。
				}
			}
			return matches;
		}
private enum MatchType {
			PRESENT { // 提供了class
				public boolean matches(String className, ConditionContext context) {
					return ClassUtils.isPresent(className, context.getClassLoader());
				}
			},
			MISSING {  // 没有提供calss
				public boolean matches(String className, ConditionContext context) {
					return !ClassUtils.isPresent(className, context.getClassLoader());
				}
			};
			public abstract boolean matches(String className, ConditionContext context);
		}

From the above code, we can see that the judgment of OnClassCondition is very simple, it is based on the class type of the value provided by the annotation, and then the reflection of class.forName is true, that is, the class is provided, if not, it is not provided. Class, and finally the getMatchOutcome method will return a match.

    complete!

 

Guess you like

Origin blog.csdn.net/shuixiou1/article/details/113276641