@SpringBootApplication
發現是一個複合註解 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 由三個註解組合而來
@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 {
...
}
-
@SpringBootConfiguration 等效於 @Configuration 註解(@Configuration 如果不清楚去看看前面 spring 的文章)
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
-
@ComponentScan 是用來配置哪些包能被掃描,注意這裡只是規定哪些被掃描,並不會把這些包下面的類沒有註冊到容器(@EnableAutoConfiguration 會讀取 @ComponentScan 配置的資訊,然後根據配置進行註冊)
簡言之就是 @ComponentScan 只管配置,@EnableAutoConfiguration 會根據配置來註冊
-
@EnableAutoConfiguration 這個是核心,完成自動配置的註解,專門來說說這個
@EnableAutoConfiguration
可以看出是由兩個註解的複合註解 @AutoConfigurationPackage、@Import(AutoConfigurationImportSelector.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
@AutoConfigurationPackage
-
這個註解的元註解可以看出,使用 @Import 匯入了一個 AutoConfigurationPackages.Registrar 型別的 bean
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { }
-
@Import 前面說的很清楚了,有三種用法
@Import(A.class) // 匯入普通類 @Import(MyImportSelector.class) // 匯入實現了 ImportSelector 的類(可以批次註冊 bean,方法返回的陣列就是要匯入的 bean) @Import(MyImportBeanDefinitionRegister.class) // 匯入實現了 ImportBeanDefinitionRegistrar 的類(也可以批次,透過註冊 BeanDefinition 的方式)
@Import(AutoConfigurationPackages.Registrar.class) 就是使用的第三種方式,看看原始碼(這是個內部類)
// org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // metadata 表示註解元資訊,獲取使用了這個註解的那個類上的註解資訊(我們是從 SpringBootApplication 跟進來的,也就是啟動類) register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); } @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImports(metadata)); } }
register 方法做了什麼(spring 的 方方面面詳細講了 @Import 怎麼註冊 BeanDefinition 的,這裡再大概看下)
// org.springframework.boot.autoconfigure.AutoConfigurationPackages#register public static void register(BeanDefinitionRegistry registry, String... packageNames) { // 如果容器中的 beanFactory.beanDefinitionMap 已經有這個 bean 了(初識起動不會有) if (registry.containsBeanDefinition(BEAN)) { BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else { // BeanDefinition 物件 GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // 註冊 BeanDefinition registry.registerBeanDefinition(BEAN, beanDefinition); } }
new PackageImports(metadata) 做了什麼,是在讀取 @ComponentScan 配置的資訊
// org.springframework.boot.autoconfigure.AutoConfigurationPackages.PackageImports#PackageImports PackageImports(AnnotationMetadata metadata) { AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false)); List<String> packageNames = new ArrayList<>(); // 先找 basePackages 屬性 for (String basePackage : attributes.getStringArray("basePackages")) { packageNames.add(basePackage); } // 再找 basePackageClasses 屬性 for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) { packageNames.add(basePackageClass.getPackage().getName()); } // 如果都沒配置,那就使用啟動類所在的包 if (packageNames.isEmpty()) { // metadata.getClassName() 啟動類的包 packageNames.add(ClassUtils.getPackageName(metadata.getClassName())); } this.packageNames = Collections.unmodifiableList(packageNames); }
這裡有一點要注意,如果什麼都不配置,只會匯入啟動類這一個包,並不是往上說的子包及以下的包
比如我定義一個包裡面放一個 Controller(UserController)為什麼這個也會被註冊到容器,剛不是說子包不會被掃描到嗎?
因為這只是 spring 的容器,springMVC 初始化的時候,自己還有個容器,會把這些再進行註冊!
@Import(AutoConfigurationImportSelector.class)
剛才的 @Import 是匯入了一個實現了 ImportBeanDefinitionRegistrar 的類,這個 @Import 匯入了一個實現了 ImportSelector 的類AutoConfigurationImportSelector 類繼承和實現關係:AutoConfigurationImportSelector > DeferredImportSelector > ImportSelector
// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 主要看這個方法返回的什麼(返回的陣列都要被匯入容器)
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry
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);
}
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
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;
}
org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// 呼叫了本類的 loadSpringFactories 方法,就是下面的方法
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
// org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
// 找所有 jar 的 META-INF/spring.factories 檔案,裡面配置的
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
....
} catch (IOException var13) {
IOException ex = var13;
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", ex);
}
}
}
找到的所有的類都要註冊到容器嗎,答案肯定不是的,不然太多了,這裡就需要按需載入了
用 spring-boot-autoconfigure 這個 jar 為例,這個很典型,springboot 自己的,裡面類容也是最多的,有的 jar 裡面跟沒沒有這個檔案
隨便拎一個 org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration 類,進去看看