Spring Boot自動裝配原理源碼分析


Spring Boot自動裝配原理源碼分析

1.環境准備

使用IDEA Spring Initializr快速創建一個Spring Boot項目

添加一個Controller類

@RestController
public class HelloController {

    @RequestMapping("hello")
    public String hello() {
        return "hello";
    }
}

主配置類如下

@SpringBootApplication
public class SpringbootQuickstartApplication {

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

}

2.注解分析

Spring Boot規定,項目的主配置類必須放在最外層包,也就是說,所有的類都必須放在主配置類的同級包或者子包里,這么做的用意是什么?我們點開@SpringBootApplication注解慢慢分析(下面代碼中省略元注解)...

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { 
	@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
	@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) 
})
public @interface SpringBootApplication {

@SpringBootApplication內部標注了三個注解:

  • @SpringBootConfiguration

    進入源碼中可以看見,@SpringBootConfiguration其實就是Spring中的@Configuration,用於標注配置類

    @Configuration
    public @interface SpringBootConfiguration {
    
  • @ComponentScan

    這個注解也是Spring中的,它用來將指定包需要裝配的組件注冊到容器中

  • @EnableAutoConfiguration

    接下來才是今天的重頭戲,Spring Boot自動配置的主角!

    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    

3.自動裝配的主角

​ 進入@EnableAutoConfiguration源碼你會發現這個注解中標注了兩個注解@AutoConfigurationPackage@Import

(1)、@AutoConfigurationPackage

點進該注解

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

在點進Register,這是一個靜態內部類

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(new PackageImport(metadata));
  }
    
}

我們在第一個方法處打一個斷點debug一下,發現new PackageImport(metadata).getPackageName()的結果其實就是一個包名,這時我們很容易的可以想到,這個包就是Spring Boot主配置類所在的包

再看一眼metadata,果然,就是主配置類

因此,這個注解的作用就是將主配置類所在的包作為自動配置包進行管理

(2)、@Import(AutoConfigurationImportSelector.class)

@Import的作用就是導入一個類到IOC容器,我們先來看一下導入的這個類:自動配置導入選擇器

源碼里有一個方法selectImports,選擇導入

@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());
}

在點進getAutoConfigurationEntry,獲取自動配置類

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   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);
}

getCandidateConfigurations方法處打一個斷點,發現configurations的結果是所有的xxxAtuoConfiguration類,一共124個,請記住這個數字。

那么這些自動配置類是如何獲取的呢,從哪里獲取的呢?

我們繼續點進getCandidateConfigurations,獲取候選的配置

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

繼續點,loadFactoryNames,加載工廠名,方法所在類中有一個常量FACTORIES_RESOURCE_LOCATION,看代碼可以清晰的看到,這方法加載classpath下的所有jar包的META-INF/spring.factories文件,結果用一個HashMap存儲

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 {
			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);
		}
	}

打開spring-boot-autoconfigure-2.2.4.RELEASE.jar/META-INF/spring.factories,文件部分類容如下,你可以點進去看看第22~145行,確實是124個全類名

# 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,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration
... ...

4.類的加載時機

加載了這么多類我們又不一定全都用得到,設計師肯定會想辦法讓類在我們需要的時候才生效,我們隨便點進一個類,可以看到一片飄紅,因為我們並沒有引入RabbitMQ相關依賴,再看一個注解ConditionalOnClass,意思就是存在某個指定的類才生效,類似的注解還有很多,都是@ConditionaOn xxx,在一定條件下類才會生效。

由於引入了web模塊,WebMvcAutoConfiguration正常顯示


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM