SpringBoot 之 自动配置原理揭秘

一、Spring Boot 自动配置相关注解说明

为了能让同学们后面阅读的时候更加专注于代码实现原理,而不是去纠结每个注解的作用,这里将Spring Boot 常用的注解进行说明,方便后面的阅读。

(1) @Value 注解

作用:

将外部的值动态注入到Bean中

方式一:注入普通字符串
@Component
public class St_annotation_value {
//  等价与 <bean class="St_annotation_value"> <property name ="name" value="xiao wang"></property></bean>
    @Value("xiao wang")
    private String name;
    // getting and setting..
}

测试下

@SpringBootTest
class StAutoconfigApplicationTests {
    @Autowired
    St_annotation_value value;
    @Test
    void contextLoads() {
    	// 打印结果 xiao wang
        System.out.println(value.getName());
    }
}
通过 PlaceHolder方式注入 @Value(${property:default value})

1.先在 application.properties加入

name=xiao wang 
  1. 代码修改为
@Component
public class St_annotation_value {
    //@Value("${key}") 格式
    @Value("${name}")
    private String name;
}
  1. 打印结果
xiao wang

(2) @Import注解

作用:

通过快速导入的方式实现把实例加入spring的IOC容器中

格式:

@Import({ 类名.class , 类名.class... })

方式一:配合 spring bean 容器使用将类对象注册为 bean对象

@Import({Person.class}) // 将 Person 对象注册为bean 对象
@Configuration // 这里也可以使用 @Component 只要是当前类是bean对象即可
public class St_annotation_import {
}

测试一下

@SpringBootTest
class StAutoconfigApplicationTests {
    @Autowired
    Person person;
    @Test
    void contextLoads() {
        //成功打印hello,说明person对象已注册为bean 对象
        person.hello();
    }
}

方式二:配合importSelector使用,批量将指定的类注册为bean

  1. 自定义一个 ImportSelector类,这里说明下ImportSelector类,当引用到ImportSelector 会调用 ImportSelector的 selectImports,将此方法返回的类 都注册为bean对象。
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 返回需要注册为bean的 类全限名
        return new String[]{"com.ym.st_autoconfig.entity.Bird"};
    }
}
  1. @Import 配合ImportSelector 使用。
@Import({MyImportSelector.class})
@Configuration
public class St_annotation_import {
}
  1. 进行测试
@SpringBootTest
class StAutoconfigApplicationTests {
    @Autowired
    Bird bird;
    @Test
    void contextLoads() {
        // 调用成功,说明bird类已经注册为bean对象
        bird.say();
    }
}

(3) @ConfigurationProperties注解

作用:

当一个类有多个属性的话,使用@Value一个个注入是不是太麻烦了,这时候可以使用@Configuration注解,他支持批量注入属性,该注解有一个prefix属性,通过指定的前缀,绑定配置文件中的配置,该注解可以放在类上,也可以放在方法上

使用:

  1. 在 application.properties加入
person.name=xiao hong
person.age=12
person.sex=
  1. 在需要注入属性的类中加入注解
@Component
// 通过前缀去找对应属性,注入到bean中
@ConfigurationProperties(value = "person")
public class St_annotation_configurationProperties {
    private String name;
    private int age;
    private String sex;
    // getting and setting..
}
  1. 测试一下
@SpringBootTest
class StAutoconfigApplicationTests {
    @Autowired
    St_annotation_configurationProperties configurationProperties;
    @Test
    void contextLoads() {
        System.out.println(configurationProperties.toString());
        // 打印结果 St_annotation_configurationProperties{name='xiao hong', age=12, sex='男'}        
    }
}

(4) @Configuration注解

作用:

一个类级别的注释,指示对象是bean定义的来源,通俗的解释就是对bean对象进行配置的地方,相当于我们以前xml配置。

使用:

@Configuration
public class St_annotation_configuration {
    //相当于 我们以前 xml 定义的 <bean id='cat' class='com.ym.st_autoconfig.entity.Cat'/>
    @Bean
    public Cat getCat(){
        return new Cat();
    }
}

(5) @Conditional注解

作用:

根据某个条件创建特定的Bean,通过实现Condition接口,并重写matches接口来构造判断条件,就是根据条件判断是否需要创建注册这个bean

使用:

  1. 自定义conditional
public class MyConditional implements Condition {
    // 会调用matches() 判断是否将此类注册为bean ,true 注册 false 拒绝注册
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return false;
    }
}
  1. 使用 conditional
@Configuration
@Conditional(MyConditional.class)
public class St_annotation_conditional {
    @Bean
    public Tiger getTiger(){
        return new Tiger();
    }
}
  1. 测试一下
@SpringBootTest
class StAutoconfigApplicationTests {
    @Autowired
    Tiger tiger;
    @Test
    void contextLoads() {
        // 调用失败,因为我们前面返回的总是false所以@configuration 没有生效,当改为true后,程序运行成功
        tiger.say();
    }

}

常用的conditional

扩展注解作用
ConditionalOnBean容器中存在指定 Bean,则生效。
ConditionalOnMissingBean容器中不存在指定 Bean,则生效。
ConditionalOnClass系统中有指定的类,则生效。
ConditionalOnMissingClass系统中没有指定的类,则生效。
ConditionalOnProperty系统中指定的属性是否有指定的值。
ConditionalOnWebApplication当前是web环境,则生效。

二、Spring Boot 自动配置原理分析

流程概述

先大致讲解下Spring Boot 自动配置的流程,这样结合流程在接下去看分析流程,可能会更加清晰一下。
1. 首先启动类上使用了 @EnableAutoCnnfiguration注解,
2. 这个注解里应用了一个@import注解,这注解又引用了自定义的 ImportSelector,
3. 自定义的ImportSelector 会去找到 META-INF/spring.factories配置文件中所有的自定配置类。
4. 然后根据@conditional注解决定是否加载这些配置类。
5. 所有配置类加载出来来通过@ConfigurationProperties 去配置文件中读取配置参数,如果没有则取默认的参数。
6. 最后将读取到的配置类都加载为bean。

详细分析

首先方法的入口肯定是SpringApplication.run() , 然后会调用refresh() 初始化上下文,最后调用 ConfigurationClassPostProcessor (BeanFactory 后置处理器) 对 配置类进行解析,这时候我们的启动类中应用了 @SpringBootApplication 这个组合注解,其中包含 @Configuration,所以会对这个启动类的注解进行解析处理。
然后我们看看 @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) })

启动 @EnableAutoConfiguraion 字面意思来看应该就是自动配置的关键了,我们点进去看看, 启动 @AutoConfigurationPackage 是自动扫描包的范围, @Import 导入了 自定义的AutoConfigurationImportSelector,之前讲过 ImportSelector 的作用是将需要注册bean返回,那么我们看看AutoConfigurationImportSelector到底返回了哪些类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    //..........
}

AutoConfigurationImportSelector 的 selectImports 方法 ,调用了 getAutoConfigurationEntry方法,最后的调用链是 selectImports -> getAutoConfigurationEntry -> getCandidateConfigurations,那我们看看 getCandidateConfigurations方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

getCandidateConfigurations 方法

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //  getSpringFactoriesLoaderFactoryClass() 返回的是 EnableAutoConfiguration.class
    //  SpringFactoriesLoader.loadFactoryNames 就是已 EnableAutoConfiguration类的全限名为key 去 META-INF/spring.factories 下面找需要的加载的类
   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;
}

在spring.factories文件中就是这样的格式

在这里插入图片描述
那么到这里会想一个问题了 EnableAutoConfiguration 的类那么多怎么来确认他需不需要加载呢? 这就用到了我们上面的说的 @Conditional了 每个配置类都有引用这个注解来判断是否去要加载,在getAutoConfigurationEntry方法中有调用

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 找出所有配置类
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   configurations = removeDuplicates(configurations);
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   // 进行过滤选出满足条件的配置类
   configurations = getConfigurationClassFilter().filter(configurations);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

到这里springboot 怎么自动去加载配置的过程差不多结束了,那么还有一个问题,现在许多都有默认配置 我们怎么去覆盖这些配置或者说有哪些可以配置的呢?这就要去看 具体的配置类了 这里已 ServletWebServerFactoryAutoConfiguration为例,点击去我们可以看到

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
      ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
      ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
      ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

重点在 @EnableConfigurationProperties 这个注解,这个注解作用是使用 @ConfigurationProperties 注解的类生效,我们点进ServerProperties

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

   /**
    * Server HTTP port.
    */
   private Integer port;

   /**
    * Network address to which the server should bind.
    */
   private InetAddress address;

   @NestedConfigurationProperty
   private final ErrorProperties error = new ErrorProperties();
    //.......... 
}

看到它引用了ConfigurationProperties注解,并且前缀是 server,那么我们定义属性的时候 只要 前缀是server 并且ServerProperties有这个属性就可以了。

最后总结

SpringBoot启动的时候会通过@EnableAutoConfiguration加载META-INF/spring.factories配置文件中的所有配置,并进行加载,这些配置的属性是通过 @ConfigurationProperties注解进行全局加载的

三、自定义Spring Boot 自动配置类

上面说了这么多还是要动手实践下才会印象深刻,那么结合上面所说的,如果我们要自定义一个自动配置类 那么我们需要什么?

  1. 在 META-INF就建立一个 spring.factories配置文件
  2. 在spring.factories 中配置上自己的自动配置类
  3. 定义一个自动配置的属性类

定义一个配置文件

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ym.st_autoconfig.configuration.MyAutoConfiguration

定义一个配置类

@ConfigurationProperties("my")
public class MyProperties {
    private String name;
    private int age;
}

定义一个自动配置类

@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {

    public MyAutoConfiguration(MyProperties myProperties){
        System.out.println(myProperties.toString());
    }
}

配置文件中增加配置

my.name=xiao hong
my.age=12

启动后观察控制台,启动成功

在这里插入图片描述
好了,springboot 自动配置原理就分析到了这里,如果有问题欢迎指出!!!谢谢大家观看

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值