在springboot项目的启动类中,都会存在一个@SpringBootApplication
注解,进入这个注解中看一看。
可以发现,在这个注解中又包含了@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
注解,大概解释一下这写注解的作用:
@SpringBootConfiguration
:这个注解里面又包含了@Configuration
注解,其实主要作用就是将当前类声明为一个配置类,可以在类中注入一些Bean;@EnableAutoConfiguration
:这个注解是实现springboot自动装配的核心注解,在下面会来说这个注解;@ComponentScan
:组件扫描,默认会扫描启动类包下的所有类,你也可以使用excludeFilters
属性来排除一些类的扫描,如上图所示。进入到这个注解中,其核心就是这个**@Import注解。
可以发现这里通过@Import注解导入了一个AutoConfigurationImportSelector类。
并且,这个类实现了ImportSelector**接口的selectImports
方法。
当@Import了某一个类时,并且这个类实现了ImportSelector中的selectImports方法,那么实现方法selectImports的返回的全类名就会被注入的容器中。
比如:
自定义了一个类实现ImportSelector,并重写了selectImports方法,可以看到该访问返回了一个类的全类名。
public class MyImportClass implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.sunao.entity.User"}; // 这里也可以有多个
}
}
那么,接下来通过@Import(MyImportClass.class)
注解,配置类加载时就会将User对象注入到容器中。
其实springboot的自动配置主要就是先去读取spring.factories配置文件中配置类的全类名,然后再通过上面这种方式将他们加载到容器中的。
我们可以下面的依赖包下找到spring.factories配置文件。
其实不仅仅是spring-boot-autoconfigure包下会有这个文件,很多第三方依赖也会自己定义spring.factories文件,它们都会被启动类扫描到。
这样一看,我们完全可以自己写一个starter,实现自动装配功能,在下面会提到。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) { // 判断是否开启了自动配置功能开关
return NO_IMPORTS;
}
// 获取需要加载到容器中的配置类信息
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
// 将这些配置类的全类名作为返回值返回 这样就会将这些配置类加载到容器中
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
再来看一下getAutoConfigurationEntry(annotationMetadata)
这个方法具体是怎么实现的。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { //
if (!isEnabled(annotationMetadata)) { // 判断是否开启了自动配置功能开关
return EMPTY_ENTRY;
}
// 获取到注解中定义的一些属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 内部调用了SpringFactoriesLoader.loadFactoryNames方法 获取spring.factories文件中定义的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 通过LinkedHashSet去掉重复的配置类 ---> 去重
configurations = removeDuplicates(configurations);
// 获取我们指定需要排除的类 比如:@SpringBootApplication(excludeName = "xxx", exclude = Xxx.class)
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 根据条件过滤掉一些不满足加载条件的配置类
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
getAttributes(annotationMetadata)
:
getCandidateConfigurations(annotationMetadata, attributes)
:
其内部主要就是调用了SpringFactoriesLoader.loadFactoryNames
方法
在调用完getConfigurationClassFilter().filter(configurations)
方法之后,可以发现过滤了很多配置类,从137 —> 27了
这个方法主要就是用来过滤掉一些不需要加载的类,比如我们很多时候会在配置类上添加该配置类加载的条件,当不满足这些条件时,就不需要去加载该类
这里面试可能会问你:spring.factories中的配置类会全部被加载吗?
当然不是,会通过getConfigurationClassFilter().filter(configurations)
这个方法过滤掉很多不需要加载的类。
最后一步,new AutoConfigurationEntry(configurations, exclusions)
通过这些全类名,将他们加载到容器中
在了解了上述的原理之后,你想自己写一个starter实现自动配置就会变得非常非常简单。
步骤:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
@Configuration
public class MyThreadPoolAutoConfiguration {
@Bean
@ConditionalOnClass(ThreadPoolExecutor.class)
public ThreadPoolExecutor myTreadPool() {
return new ThreadPoolExecutor(520, 1314, 2000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
}
}
表示在springboot启动时,我们要将MyThreadPoolAutoConfiguration注入到容器中,实现自动配置;
<dependency>
<groupId>com.sunaogroupId>
<artifactId>my-auto-config-boot-starterartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
@Slf4j
@SpringBootApplication
public class SpringbootDemo01Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootDemo01Application.class, args);
ThreadPoolExecutor myThreadPool = run.getBean(ThreadPoolExecutor.class);
log.info("core pool size: {}, max pool size: {}", myThreadPool.getCorePoolSize(), myThreadPool.getMaximumPoolSize());
}
}