整理一份SpringBoot自动配置实现原理


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包中

捕获.PNG

当程序运行完SpringFactoriesLoader.getCandidateConfigurations方法后,会获取相应的候选的自动配置类

捕获.PNG
如上图所示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>

运行后集合中包含了很多配置的全路径类名类名信息

捕获.PNG

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值