theme: juejin
前言
众所周知,SpringBoot帮助我们省去了过去各种繁琐的编辑配置文件的工作;虽然这并没有让程序员的工作变的更轻松,但确确实实提高了开发的效率,让我们有时间去处理其他的问题。
SpringBoot启动类
@SpringBootApplication
public class Bootdemo01Application {
public static void main(String[] args) {
SpringApplication.run(Bootdemo01Application.class, args);
}
}
@SpringBootApplication是一个复合注解,其中包含以下注解
@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}
)}
)
前四行省略不看,第五行 @SpringBootConfiguration 表示这是一个SpringBoot配置类,第七行 @ComponentScan扫描要加载到容器的类,排除不需要的类。@SpringBootConfiguration和 @ComponentScan与自动配置核心内容没太大关系,不多做介绍。
现在就只剩下 @EnableAutoConfiguration 这一个注解了。
@EnableAutoConfiguration
字面意思是开启自动配置,一点儿问题没有。然后发现在此注解的定义中还标注着两个注解,所以这里应该就是自动配置的关键所在了。这里面有两个注解,从上到下依次说明
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {}
@AutoConfigurationPackage
看字面意思是自动配置包,猜一猜肯定是对包里面的内容进行操作的,点进去之后发现里面包含一个
@Import({AutoConfigurationPackages.Registrar.class}) 注解,看来是导入了一个跟注册相关的class。先是一个自动配置包,接着是注册,难道是注册包中的类?点开Registrar内部类看一下
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
发现实现了ImportBeanDefinitionRegistrar,这个接口是spring提供给我们的用来将Bean配置信息注册到BeanDefinition用的,registerBeanDefinitions方法就是这个接口提供的。当我去运行程序到 (new PackageImports(metadata)).getPackageNames() 时,发现结果是SpringBoot启动类所在的包名。接着来到register方法中,看到一条关键代码
registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
BasePackagesBeanDefinition中记录了要扫描的包名
BasePackagesBeanDefinition(String... basePackages) {
this.setBeanClass(BasePackages.class);
this.setRole(2);
//basePackages 是要扫描的报名
this.addBasePackages(basePackages);
}
basePackages是要扫描的报名,这里的值就是SpringBoot启动类所在包的包名。
这里的包名是SpringBoot启动类所在包,我们可以推断出springboot会帮我们扫描这个包及其子包,并将包下的需要被注册的Bean注册到BeanDefinition中。所以在日常开发中,我们把要加入到容器的类建立在SpringBoot启动类所在包或其子包下,为的就是希望能被Springboot扫描到
通过对 @AutoConfigurationPackage 这个注解的分析,发现它的作用就是设置当前配置所在的包作为扫描包,后续对这个包进行扫描
@Import({AutoConfigurationImportSelector.class})
进入AutoConfigurationImportSelector 内部主要看下面这个方法
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
});
//看这里
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
while(var4.hasNext()) {
String importClassName = (String)var4.next();
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
}
进入上下文中的 getAutoConfigurationEntry(annotationMetadata) 方法后看下面这一行代码
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
这行代码的作用是获取springboot候选的配置,也就是获取springboot提前定义好的一些配置,具体从哪配置呢?进入到getCandidateConfigurations方法中查看
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//这里是一个存储配置信息的集合
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
这里我们看到通过SpringFactoriesLoader.loadFactoryNames方法获取了一个存储配置的集合,打开这个方法发现内部实际上又调用了一个方法来获取集合,具体方法如下
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
/* 省略代码 */
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
/* 省略代码 */
}
打开后发现这段代码加载了一个文件,这个文件中包含了很多需要自动配置的第三方的引用,文件位置在spring-boot-autoconfigure包中
当程序运行完SpringFactoriesLoader.getCandidateConfigurations方法后,会获取相应的候选的自动配置类
如上图所示List集合中包含一个com.github.onblog.redislock.RedisLockAutoConfiguration,之所以会被加载到List中是因为我在pom文件中引入了相关的依赖。但是最终这个配置类是否会被加载到spring容器中,取决于是否满足先决条件
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({RedisTemplate.class})
@ComponentScan
public class RedisLockAutoConfiguration {
public RedisLockAutoConfiguration() {
}
}
这个配置类中包含了一个 @ConditionalOnClass({RedisTemplate.class}) 注解,这个注解的意思是说只有系统中存在RedisTemplate.class时才会将配置类加载到容器。由于现在并没有引入相关的依赖,系统中不存在RedisTemplate.class,所以这个配置类不会被加载到容器。
现在删除上述redislock,并引入以下坐标后再运行
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
运行后集合中包含了很多配置的全路径类名类名信息
RedisAutoConfiguration
这是一个帮我们自动配置Redis的配置类
@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
//下面的注解表示引入reids的两种客户端实现
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
@Bean
//如果现在容器中不存在redisTemplate,才加载这个bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
//如果现在没有提供redis连接工厂则加载,才加载这个bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
首先配置类的加载条件成立,即 @ConditionalOnClass({RedisOperations.class}) 成立,其次类中包含一个自动配置属性的注解
@EnableConfigurationProperties({RedisProperties.class})
这个注解帮助我们完成RedisProperties中属性的加载,属性的值从application.yml文件中来
@ConfigurationProperties(
prefix = "spring.redis"
)
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String username;
private String password;
private int port = 6379;
/* 省略代码 */
}
这里的属性的值就是Redis的默认配置,@ConfigurationProperties(
prefix = “spring.redis”
) 表示从application.yml配置文件中读取属性的值,如下所示:
spring:
redis:
port: 6377
现在redis客户端连接的redis端口就变成了6377