SpringBoot原始碼解讀系列三——引導註解

為了生活,加油發表於2022-02-11

我們再來看下SpringBoot應用的啟動類:

檢視程式碼
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;


@SpringBootApplication
public class DemoApplication {

    @RequestMapping("/test")
    @ResponseBody
    public Map<String,String> test(){
        Map<String,String> map = new HashMap<>();
        map.put("key","Test");
        return map;
    }

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

}

這裡有一個核心的註解@SpringBootApplication,我們跟進去看一下,會看到這個是在SpringBoot1.2.0版本引進的,其中註釋是這樣的:

Indicates a configuration class that declares one or more @Bean methods and also triggers auto-configuration and component scanning. This is a convenience annotation that is equivalent to declaring @Configuration, @EnableAutoConfiguration and @ComponentScan.

翻譯一下:標示一個配置類,宣告一個或多個@Bean方法,並觸發自動配置和元件掃描。這是一個便利的註解,相當於宣告@Configuration, @EnableAutoConfiguration和@ComponentScan。這其實是一個註解的集合,原始碼看了下相當於集中了

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

三個註解。下面分三個部分來介紹:

1、@SpringBootConfiguration註解

跟進原始碼看一下,這個是在SpingBoot 1.4.0引入進來的註解,其主要還是在於又引進了@Configuration註解,如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration 

再看下注釋

Indicates that a class provides Spring Boot application @Configuration. Can be used as an alternative to the Spring's standard @Configuration annotation so that configuration can be found automatically (for example in tests).
Application should only ever include one @SpringBootConfiguration and most idiomatic Spring Boot applications will inherit it from @SpringBootApplication.

翻譯:標識類提供Spring Boot應用程式@Configuration。可以作為Spring的標準@Configuration註釋的替代品,這樣可以自動找到配置(例如在測試中)。應用程式應該只包含一個@SpringBootConfiguration,大多數習慣用法的SpringBoot應用程式都會從@SpringBootApplication繼承它。

這說明,這個來源於Spring原生的@Configuration,跟進去看確實是用到了Spring的Configuration註解,這個就不繼續跟了,有興趣可以詳見後續的Spring原始碼解析。

其實,跟到這裡,會發現這個註解的作用就是在於標識這個類是能夠被註冊到Spring容器中的,類似於宣告為一個Spring Bean而已。

2、@EnableAutoConfiguration註解

這個註解是在SpringBoot1.0.0就被引入進來了,其父註解主要是@AutoConfigurationPackage,

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

同樣,我們可以看下官網是怎麼說的:

Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined. For example, if you have tomcat-embedded.jar on your classpath you are likely to want a TomcatServletWebServerFactory (unless you have defined your own ServletWebServerFactory bean).
When using @SpringBootApplication, the auto-configuration of the context is automatically enabled and adding this annotation has therefore no additional effect.
Auto-configuration tries to be as intelligent as possible and will back-away as you define more of your own configuration. You can always manually exclude() any configuration that you never want to apply (use excludeName() if you don't have access to them). You can also exclude them via the spring.autoconfigure.exclude property. Auto-configuration is always applied after user-defined beans have been registered.
The package of the class that is annotated with @EnableAutoConfiguration, usually via @SpringBootApplication, has specific significance and is often used as a 'default'. For example, it will be used when scanning for @Entity classes. It is generally recommended that you place @EnableAutoConfiguration (if you're not using @SpringBootApplication) in a root package so that all sub-packages and classes can be searched.
Auto-configuration classes are regular Spring @Configuration beans. They are located using the SpringFactoriesLoader mechanism (keyed against this class). Generally auto-configuration beans are @Conditional beans (most often using @ConditionalOnClass and @ConditionalOnMissingBean annotations).

翻譯:啟用Spring Application Context的自動配置,嘗試猜測和配置您可能需要的bean。自動配置類通常基於您的類路徑和您定義的bean應用。例如,如果你的類路徑中有tomcat-embed.jar,你可能需要一個TomcatServletWebServerFactory(除非你已經定義了自己的ServletWebServerFactory bean)。當使用@SpringBootApplication時,上下文的自動配置會被自動啟用,因此新增這個註釋不會有額外的效果。自動配置試圖儘可能地智慧,當您定義更多自己的配置時,自動配置就會失效。您總是可以手動排除()任何您不想應用的配置(如果您沒有訪問它們,則使用excludeName())。你也可以通過spring. autoconfiguration.exclude屬性來排除它們。自動配置總是在註冊了使用者定義的bean之後應用。被@EnableAutoConfiguration註解的類的包,通常是通過@SpringBootApplication註釋的,具有特定的意義,通常被用作“預設”。例如,它將在掃描@Entity類時使用。通常建議您將@EnableAutoConfiguration(如果您不使用@SpringBootApplication)放在根包中,以便可以搜尋所有的子包和類。自動配置類是常規的Spring @Configuration bean。它們是使用springfactoryesloader機制來定位的(針對這個類)。通常自動配置bean是@條件bean(最常用的是@ConditionalOnClass和@ConditionalOnMissingBean註釋)。

從這裡我們可以總結出幾點:

  • 開啟自動配置功能
  • 以前使用Spring需要配置的資訊,Spring Boot幫助自動配置;
  • @EnableAutoConfiguration通知SpringBoot開啟自動配置功能,這樣自動配置才能生效。

跟進去繼續分解下,看一下@AutoConfigurationPackage

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

我們看到是用到了AutoConfigurationPackages.Registrar.class,這個預設將主配置類(@SpringBootApplication)所在的包及其子包裡面的所有元件掃描到Spring容器中。如下

檢視程式碼

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
             //預設將會掃描@SpringBootApplication標註的主配置類所在的包及其子包下所有元件
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

另外看一下@Import(AutoConfigurationImportSelector.class)

其實,EnableAutoConfigurationImportSelector: 匯入哪些元件的選擇器,將所有需要匯入的元件以全類名的方式返回,這些元件就會被新增到容器中。

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
檢視程式碼

protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

其中重點是這個

List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

會給容器中注入眾多的自動配置類(xxxAutoConfiguration),就是給容器中匯入這個場景需要的所有元件,並配置好這些元件。我們跟進去看看:

檢視程式碼

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;
	}

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

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

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			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();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

我們可以看到:SpringBoot啟動的時候從類路徑下的 META-INF/spring.factories中獲取EnableAutoConfiguration指定的值,並將這些值作為自動配置類匯入到容器中,自動配置類就會生效,最後完成自動配置工作。EnableAutoConfiguration預設在spring-boot-autoconfigure這個包中。注:關於自動配置又是一門學問了,另外開篇講解

3、@ComponentScan註解

這個@ComponentScan是直接繼承Spring的@ComponentScan註解的,主要是定義掃描的路徑從中找出標識了需要裝配的類自動裝配到spring的bean容器中。

總結

本文詳細分析了SpringBoot的啟動類註解@SpringBootApplication,當然這還涉及到底層Spring的註解,有興趣可以自行研究或者參考後續要推出的Spring5+的原始碼解析。

相關文章