用过SpringBoot的同学对下面的代码都不会陌生,这段代码十分简洁,为了学习Spring Boot,我们可以先从 这段代码的两个核心部分开始,也就是:
- 一个注解 @SpringBootApplication
- 一个方法SpringApplication.run()
本文主要带大家来看看这“一个注解 @SpringBootApplication”,至于“一个方法SpringApplication.run()”,等有空的时候再来补充一下。
/**
* @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
*/
@SpringBootApplication
public class HelloWorldMainApplication {
public static void main(String[] args) {
// Spring应用启动起来
SpringApplication.run(HelloWorldMainApplication.class,args);
}
}
1 开局一注解
@SpringBootApplication 是一个组合注解,它标注在某个类上,则说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用。
好奇心促使我们“点”进去看看:
@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
- @EnableAutoConfiguration
- @ComponentScan
闲话少说,让我们一个一个来看。
2 全靠三兄弟
2.1 老大@SpringBootConfiguration
我们先来看看老大@SpringBootConfiguration长什么样的:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
不难看出,@SpringBootConfiguration只有一个@Configuration,相当于这个老大其实就是一个@Configuration,表示当前类就是一个需要注册到IOC容器的组件。
2.2 老二@EnableAutoConfiguration
接下来再看看老二@EnableAutoConfiguration,从注解的字面意思上来理解,这个注解应该就和自动配置相关了。
带上了这个配置,就是告诉SpringBoot开启自动配置功能;这样自动配置才能生效。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //2.2.1
@Import(AutoConfigurationImportSelector.class) //2.2.2
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be applied.
*/
String[] excludeName() default {};
}
既然是和自动配置相关的注解,内容肯定不少,果然,这个老二还有两个“跟班”:
- @AutoConfigurationPackage
- @Import(AutoConfigurationImportSelector.class)
2.2.1 @AutoConfigurationPackage
老二的第一个跟班是一个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
在这个注解里面,又发现了一个 @Import(AutoConfigurationPackages.Registrar.class),@Import的作用是往IOC容器中添加一个组件,而这里添加的是一个子类Registar,那我们就来看看这个子类到底是什么东西:
package org.springframework.boot.autoconfigure;
public abstract class AutoConfigurationPackages {
private static final String BEAN = AutoConfigurationPackages.class.getName();
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//通过metadata获取[主程序类]的包名
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
//将[主程序类]所在包及所有子包下的组件到扫描到spring容器中
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0,addBasePackages(constructorArguments, packageNames));
} else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
}
结合代码,我们可以得出结论:@AutoConfigurationPackage注解,通过@Import(AutoConfigurationPackages.Registrar.class)将主程序类所在包及所有子包下的组件到扫描到spring容器中!
2.2.2 @Import(AutoConfigurationImportSelector.class)
老二的第二个跟班,就是一个@Import,这个@Import向IOC容器中导入了AutoConfigurationImportSelector组件,而这个组件是自动配置的核心中的核心,它决定了到底需要为SpringBoot程序导入多少组件!下面的源码非常长,初次阅读可以先记住下面的结论,全篇看完后再回过头来细读:
结论:@AutoConfigurationPackage注解,通过@Import(AutoConfigurationImportSelector.class)告诉SpringBoot,需要自动导入的组件有哪些!
selectImports源码
AutoConfigurationImportSelector的核心方法是public String[] selectImports(AnnotationMetadata annotationMetadata)。
这个方法返回的是一个字符串数组,数组的每个元素实际是一个由SpringBoot默认实现的一个个配置类的全类名,在SpringBoot启动的时候会根据这个方法返回的数组来加载相应的配置类!
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {//判断是否开启自动装配(找出spring.boot.enableautoconfiguration参数是否为true)
return NO_IMPORTS; //空数组
}
//获取所有的自动配置元信息(mataData)(内部维护了Properties),详见源码1
//实际上项目下所有【spring-autoconfigure-metadata.properties】配置文件的key-value值
AutoConfigurationMetadata autoConfigurationMetadata =
AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//将注解元信息封装成注解属性对象(底层是个LinkedHashMap)
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//获取SpringBoot已写好的自动配置组件
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 StringUtils.toStringArray(configurations);
}
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = getAnnotationClass().getName(); //这里实际就是EnableAutoConfiguration的全类名
//AnnotationAttributes是LinkedHashMap<String, Object>的子类
//这里就是将EnableAutoConfiguration的参数转成Map返回
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes,
() -> "No auto-configuration attributes found. Is "
+ metadata.getClassName() + " annotated with "
+ ClassUtils.getShortName(name) + "?");
return attributes;
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes) {
//加载所有的SpringBoot已经写好的自动配置类,详见源码2
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;
}
}
源码1:selectImports()中的loadMetadata
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/"+"spring-autoconfigure-metadata.properties";
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
//获取项目下所有【META-INF/spring-autoconfigure-metadata.properties】的路径资源
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path));
//提取所有同名资源文件中的所有key-value数据
Properties properties = new Properties();
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
//最后将【Properties】封装成【AutoConfigurationMetadata】返回
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException(
"Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
源码2:getCandidateConfigurations()的loadFactoryNames()
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, 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 {
//获取项目下所有【META-INF/spring.factories】的路径资源
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
//加载这些资源文件的所有key-value参数到MultiValueMap中并返回
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()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
2.3 老三 @ComponentScan
这个老三就不是SpringBoot特有的新注解了,我们在Spring也经常会用到,主要是起到扫描并将扫描到的组件加入到IOC容器中,从这个@ComponentScan参数中可以看出,这里是【excludeFilters】,意味着,这整个注解就是为了去掉多余的组件。
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
我们再看,使用到了两个@Filter,类型都是CUSTOM(表示自定义),那意味着这个注解的核心就是这两个@Filter的classes了。
换句话说:这两个classes就是SpringBoot为了排除掉一些多余的组件而自定义的组件过滤器。
2.3.1 TypeExcludeFilter
下面的代码主要是从beanFactory中获取所有的TypeExcludeFilter,并执行他们的match方法,只要匹配上了就返回true。
这个过滤器其实可以理解成一个口子,在程序启动时,SpringBoot会加载Spring Bean池中所有针对TypeExcludeFilter的扩展,并遍历调用他们的match方法。 所以只要继承TypeExcludeFilter并重写match方法,都将会在该方法被调用,从而达到对客户代码的扩展
需要注意的是,因为这个过滤器在Springboot中的使用方式为:excludeFilters ,意味着,返会true的组件是要被排除的组件!
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
if (this.beanFactory instanceof ListableBeanFactory
&& getClass() == TypeExcludeFilter.class) {
//
Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory) this.beanFactory)
.getBeansOfType(TypeExcludeFilter.class).values();
for (TypeExcludeFilter delegate : delegates) {
if (delegate.match(metadataReader, metadataReaderFactory)) {
return true;
}
}
}
return false;
}
2.3.1 AutoConfigurationExcludeFilter
这个Filter主要是过滤掉【没有开启某配置的相应注解】或者【没有发现某配置必须的属性】的组件。
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
//判断【是否为配置类】+【是否满足自动配置】
return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
}
//判断是否为配置类:判断当前组件是否带有@Configuration这个注解
private boolean isConfiguration(MetadataReader metadataReader) {
return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
}
//判断是否满足自动配置:判断当前组件是否是autoConfigurations中的一员
private boolean isAutoConfiguration(MetadataReader metadataReader) {
return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}
//获取autoConfigurations,即MATE/INF下的
protected List<String> getAutoConfigurations() {
if (this.autoConfigurations == null) {
//这行代码就是去读取MATE/INF下的所有配置类全类名
this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
this.beanClassLoader);
}
return this.autoConfigurations;
}
3 小结
- @SpringBootApplication
- 1 @SpringBootConfiguration:表明为注解类
- 2 @EnableAutoConfiguration:自动装配核心
- 2.1 @AutoConfigurationPackage
- @Import(AutoConfigurationPackages.Registrar.class):导入启动类包下的所有组件
- 2.2 @Import(AutoConfigurationImportSelector.class):导入符合自动装配组件的所有组件
- 2.1 @AutoConfigurationPackage
- 3 @ComponentScan:排除对象
- 3.1 TypeExcludeFilter 继承TypeExcludeFilter的子类在此执行其match方法
- 3.2 AutoConfigurationExcludeFilter 过滤是否为配置类且带有Auto配置的注解