SpringBoot中遍布注解,如果无法理解其作用,也就相当于主动放弃了其强大的灵活性,它核心的“组合”一说也就无从谈起
源头——@SpringBootApplication
@SpringBootApplication
是SpringBoot开启的源头注解,进入源代码,发现除去元注解,剩余三个Spring包下的注解,分别为:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
第一个注解@SpringBootConfiguration
:
进入@SpringBootConfiguration,一共以下三个注解:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Configuration
最上面的三个元注解和@SpringBootConfiguration是相同的,仅剩下一个注解@Configuration
,这个注解非常熟悉,它就是Spirng中的“注解类”注解,因此,@SpringBootConfiguration本质上就是一个@Configuration,没必要再深入分析了。
第二个注解@EnableAutoConfiguration
:
Spring中以@Enable开头的注解有一个共同特点——通过@Import注解引入特定的JavaConfig类,再对其进行注册
该注解除去元注解后,剩余:
- @AutoConfigurationPackage
- @Import(AutoConfigurationImportSelector.class)
第一个@AutoConfigurationPackage
从名字就可以看出来——自动配置包——它必然就是源头注解可以自动扫描的核心注解。
点开源代码如下:
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}
发现除去元注解,仅存在一个@Import
注解
根据注释说明,知道了这个类用作储存自动配置的包,用作之后参考,其源码为一个抽象类,它所调用的静态内部类Registrar
的注释为:
to store the base package from the importing configuration.
到这里就很明显了,根据Registrar
的注释说明,它会重写实现的接口ImportBeanDefinitionRegistrar
,从而存储导入的基础包,在重写的方法registerBeanDefinitions()处打上断点,可以看到:
可见此方法将自己的项目添加进了SpringBoot Application。
于是我们再回到@EnableAutoConfiguration
内的第二个注解@Import
中,显然,这又是一个导入注解,因此,我们只关心它导入的是什么就ok。
进入@Import
导入的@AutoConfigurationImportSelector
类,这个类的代码有几百行,但需要关注的就仅有几个方法:
首先从下面的方法开始探索:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
注意上面的List<String> configurations 列表,它首先会预加载SpringBoot所有组件名:
具体通过下面的方法实现:
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;
}
将配置存储在configurations中,而该变量通过SpringFactoriesLoader类处理,于是需要进去看看源码,我们关心的是loadFactoryNames
这个方法:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
从返回值中,注意到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 {
// 注意下面这行代码,它会从 META-INF/spring.factories 这个文件中获取需要的加载的资源
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
// 下面的while会找到需要加载资源备用
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
然后,通过filter(configurations, autoConfigurationMetadata);方法实现了真正需要加载的组件:
至于这个筛选是怎么进行的,下面继续探究。
不难发现,这些组件有个共同特点,它们的组件名都是“xxxxAutoConfiguration”,说明它们必然有些被加载、筛选的共性。
找到项目路径:
任意点开spring.factories
资源文件的一个类:
@ConditionalOnProperty
这个注解又什么作用呢,进入之后,发现除去元注解,只有一个@Conditional
,顾名思义,就是这个配置有使用条件。点进这个注解有这么一行注释说明:
Indicates that a component is only eligible for registration when all specified conditions match.
猜想是对的,spring.factories
资源文件下的很多资源都是有使用条件的,因此并不会全部加载。
又要回到源头注解@SpringBootApplication
中了,只剩最后一个注解@ComponentScan
了,简单说明一下就好。
- @Controller
- @Service
- @Repository
- @Component
类似注解的包,在@SpringBootApplication
中,这个注解内的参数为:
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
excludeFilters 这个参数为设置不需要扫描的包。