SpringBoot自动配置原理快速入门

本文深入剖析 Spring Boot 的自动配置原理,从基础概念出发,详细介绍 @Conditional 派生注解的作用,以及 DeferredImportSelector 的重要性。通过具体案例阐述自动配置类是如何被加载到 Spring 容器中的。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.  基础介绍

1.1 什么是Spring?

     我们可以理解为一个容器,一个Map,这个Map里面有我们所有配置的Bean,这样我们就不用每次new Bean,而是由Spring帮我们实例化.

1.2 什么是SpringBoot

     基于Spring的脚手架框架,我们以前使用Spring的时候,无论整合MyBatis,Redis,全部需要自己实例化并且配置,并且配置许多的XML,但是有了SpringBoot之后,我们只需要引入对应的依赖,配置文件配置好,他就自动帮我们实例化了对应的Bean,我们就可以直接使用.

1.3 什么是Beandefinition

     Beandefinition中文是Bean定义,就是描述Bean的bean,你就把他当成一个pojo,里面有很多属性,例如我的类名,是否懒加载,是否自动注入等等.Spring就会根据beandefinition去把我们的Bean实例化.Spring的Bean和我们普通的Bean不同之处在于,Spring的Bean多了很多属性,是被包装过的.

例如我们下图我的User的Beandefinition,他有众多属性,是否懒加载,是否单例等等,这个Beandefinition就是描述我User的类,后面Spring会根据类名反射创建我们的Bean,而且我们的bean是拥有众多属性的Bean.

1.4 Spring引入Bean的方式

    1.@Component,@Controller,@Service等等

    2.@Bean

    3.@Import

    @Import,我们比较少使用,但是他却是springboot中重要的存在,首先他的使用很简单,例如下图,直接导入我们想要的Bean就可以了,他相比我们的@Component,优势在于我@Import可以任意导入一个不在我Springboot扫描(@ComponentScan)的路径下的类和JAR里面的类,而且可以批量导入,这一点是@Component做不到的,所以我们在引入第三方的Bean基本都有@Import

@Import({Parent.class,
        // 这是Spring-code包里面的Bean,我随便找的一个
        AntPathMatcher.class})

   4.@Import结合ImportSelector和DeferredImportSelector

     使用@Import的时候,它的类可以是实现了ImportSelector或者DeferredImportSelector接口的类。 Spring容器会实例化这个实现类,并且执行其selectImports方法例如@Import(MyImportSelector.class)这样就可以往我们Spring容器注入MyImportSelector,并且执行selectImports()方法,这个方法会返回一连串的类名,让spring去注入返回的类.

注意:

       DeferredImportSelector特殊一点,他会判断有没有实现里面的getImportGroup()方法,如果这个方法返回空则直接执行selectImports()方法,否则需要返回一个实现了DeferredImportSelector.Group的类,并且会执行返回类的Group.process()方法和Group.selectImports()方法,这里不懂没关系只需要下面讲的源码里面,调用了process()方法

public class MyImportSelector implements ImportSelector
        //虽然不能@Autowired,但是实现了这些接口是可以感知到的,下面看源码会发现,Spring会给他注入进去
        // 这样我们就可以根据特定的条件,来决定某些Bean能注入,有些Bean不能注入了
        //,BeanClassLoaderAware,BeanFactoryAware,EnvironmentAware,ResourceLoaderAware
{


    // 备注:这里各种@Autowired的注入都是不生效的,都是null
    // 了解Spring容器刷新过程的时候就知道,这个时候还没有开始解析@Autowired,所以肯定是不生效的
    @Autowired
    private HelloService helloService;

    /**
     * 容器在会在特定的时机,帮我们调用这个方法,向容器里注入Bean信息
     *
     * @param importingClassMetadata 包含配置类上面所有的注解信息,以及该配置类本身
     *                               若有需要,可以根据这些其它注解信息,来判断哪些Bean应该注册进去,哪些不需要
     * @return 返回String数组,注意:都必须是类的全类名,才会被注册进去的(若你返回的全类名不存在该类,容器会抛错)
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        System.out.println("this MyImportSelector...");
        //return new String[]{"com.fsx.bean.Child"};
        // 一般建议这么玩 用字符串写死的方式只是某些特殊场合(比如这个类不一定存在之类的。。。)
        return new String[]{Child.class.getName()};
        
    }
}

1.5  当我们在Springboot中看到了EnableXXXXX,的时候他一定是在里面用了@Import导入了某一种自动配置的类,例如下图的事务管理器.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
...
}

1.6 Spring创建Bean的简易过程

1.首先扫描我们的注解,获取哪一些Bean是我们需要的

2.把这些需要的Bean转化为我们的Beandefinition,放进我们BeandefinitionMap

3.Spring根据Beandefinition通过反射创建我们的Bean.

2.  springboot自动配置原理

  有了之前前置知识之后,我们快速来浏览一下Springboot自动配置原理

2.1 首先我们找到启动类的@SpringBootApplication,发现里面有一个@EnableAutoConfiguration

2.2 看到EnableXXX就知道里面肯定用了@Import加载一些自动配置Bean

 2.3 上面看到@Import导入了AutoConfigurationImportSelector并且实现了DeferredImportSelector,我们知道他会调用selectImports()方法,实际上DeferredImportSelector实现了getImportGroup方法,如果没实现这个方法则直接调用selectImports(),但是实现了,所以该方法返回了自定义Group类,是Group类的process调用的getAutoConfigurationEntry方法,这里不懂没关系我们只需要知道他调用了getAutoConfigurationEntry(),然后这个方法里面调用了GetCandidateConfigurations();

        GetCandidateConfigurations()方法会读取jar包所有的META_INF/spring.factories文件,这个文件里面全部都是自动配置类AutoConfiguration,那么就会将得到所有需要自动配置的类名,然后通过读取spring.factories 中的OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤,因为不是所有的自动配置类都需要注入到Spring容器.

AutoConfigurationImportSelector:

  如果没找到任何spring-factories,就会报错,有就返回spring-factories里面配置的所有类名.

AutoConfigurationGroup:

getAutoConfigurationEntry()

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {	

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 从META-INF/spring.factories中获得候选的自动配置类
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // 排重
   configurations = removeDuplicates(configurations);
   //根据EnableAutoConfiguration注解中属性,获取不需要自动装配的类名单
   Set<String> exclusions = getExclusions(annotationMetadata, attributes); 
   // 根据:@EnableAutoConfiguration.exclude
   // @EnableAutoConfiguration.excludeName
   // spring.autoconfigure.exclude  进行排除
   checkExcludedClasses(configurations, exclusions);
   // exclusions 也排除
   configurations.removeAll(exclusions);
   // 通过读取spring.factories 中的OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤
   configurations = getConfigurationClassFilter().filter(configurations);
   // 这个方法是调用实现了AutoConfigurationImportListener  的bean..  分别把候选的配置名单,和排除的配置名单传进去做扩展
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

2.4 例如sping.factories中有一个RedisAutoConfiguration,我2.3的@Import会把这个配置类加载到Spring容器当中

       

        这个配置类里面有个@Bean redisTemplate,我们用springboot的时候,直接写一下redis的配置,直接注入redisTemplate即可使用,因为他已经帮我注入进去容器了,不需要自己再@Bean去创建.

  •  那么RedisAutoConfiguration@ConditionalOnClass(RedisOperations.class)是什么意思?

        如果我的类路径下有RedisOperations.class,Spring你就帮我实例化RedisAutoConfiguration吧,RedisOperations.class 这个类在spring-data-redis这个包里面,意思就是我只要pom引入了spring-data-redis,你就帮我实例化RedisAutoConfiguration这个Bean吧.

  •  那么redisTemplate上面的@ConditionOnMissingBean是什么意思呢?

       如果我的项目不需要他的自动配置了,我要用自己的定制化,@Bean去注入RedisTemplate,这个时候ConditionOnMissingBean这个注解就会判断,我的容器里面有自己定制化的RedisTemplate,不需要再用自动配置的RedisTemplate.因为spring中有两个同名的bean会报错.

  • 如果我们使用Spring默认的RedisTemplate,那么他的默认配置在哪呢?

       答案就在注解@EnableConfigurationProperties(RedisProperties.class)的RedisProperties里面,如果我不做任何redis的配置,默认连接127.0.0.1:6379

RedisAutoConfiguration:

 RedisProperties:

2.4 总结

      任何一个springboot应用,都会引入spring-boot-autoconfigure,而spring.factories文件就在该包下面。spring.factories文件是Key=Value形式,多个Value时使用,隔开,该文件中定义了关于初始化,监听器等信息,而真正使自动配置生效的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,如下所示:会获取到自动配置,MybatisPlusLanguageDriverAutoConfiguration和MybatisPlusAutoConfiguration

      但是自动配置是否生效就看我满不满足@Conditional注解,例如下面mybatisPlus,我类路径下都没有,SqlSessionFactory这个类,我有可能数据库的包都没引进来,我初始化mybatis干嘛呢?如果满足所有条件会将自动配置类实例,并且类里面带@Bean的也会实例化到IOC容器中,我直接就可以使用.

3   @Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

@Conditional扩展注解作用

(判断是否满足当前指定条件)

@ConditionalOnJava

系统的java版本是否符合要求

@ConditionalOnBean

容器中存在指定Bean;

@ConditionalOnMissingBean

容器中不存在指定Bean;

@ConditionalOnExpression

满足SpEL表达式指定

@ConditionalOnClass

系统中有指定的类

@ConditionalOnMissingClass

系统中没有指定的类

@ConditionalOnSingleCandidate

容器中只有一个指定的Bean,或者这个Bean是首选Bean

@ConditionalOnProperty

系统中指定的属性是否有指定的值

@ConditionalOnResource

类路径下是否存在指定资源文件

@ConditionalOnWebApplication

当前是web环境

@ConditionalOnNotWebApplication

当前不是web环境

@ConditionalOnJndi

JNDI存在指定项

3.1 我们怎么知道哪些自动配置类生效;

我们可以通过设置配置文件中:启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

3.2   以HttpEncodingAutoConfiguration(Http编码自动配置)**为例说明自动配置原理;

该注解如下:

  • @Configuration:表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件。
  • @ConditionalOnWebApplication:Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效。
  • @ConditionalOnClass:判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器。
  • @ConditionalOnProperty:判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的。
  • @EnableConfigurationProperties({ServerProperties.class}):将配置文件中对应的值和 ServerProperties绑定起来;并把 ServerProperties加入到 IOC 容器中。并注册ConfigurationPropertiesBindingPostProcessor用于将@ConfigurationProperties的类和配置进行绑定

       ServerProperties通过 @ConfigurationProperties 注解将配置文件与自身属性绑定。

对于@ConfigurationProperties注解小伙伴们应该知道吧,我们如何获取全局配置文件的属性中用到,它的作用就是把全局配置文件中的值绑定到实体类JavaBean上面(将配置文件中的值与ServerProperites绑定起来),而@EnableConfigurationProperties主要是把以绑定值JavaBean加入到spring容器中.我们在application.properties 声明spring.application.name 是通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上,然后再通过@EnableConfigurationProperties注解导入到Spring容器中.

4. 为什么我们需要用DeferredImportSelector

4.1 延迟特性

beandefintion的注册顺序:

  • 1.先@ComponentScan的:@Component,@Service,@Controller
  • 2.@lmport的:@Component,@Service,@Controller
  • 3.@Configuration -->和该配置类里面的@Bean --->该配置类里面的@lmport进来的实现了lmportBeanDefinitionRegistrar接口的
  • 4.@lmport进来的@Configuration --->和该配置类里面的@Bean --->该配置类里面的@lmport进来的实现了lmportBeanDefinitionRegistrar接口
  • 5.@lmport进来的实现了DeffredlmportSelector接口的@Configuration --->和该配置类里面的@Bean --->该配置类里面的@lmport进来的实现了lmportBeanDefinitionRegistrar接口

    总结一下就是@Import导入进来的实现DeferredImportSelector,通过他的selectImport加载的类的解析是靠后的.

    假如我通过selectImport导入进来的类比@Component导入的优先会发生什么问题?

    如下图如果我先加载selectImport的RedisTemplate,那我的注解ConditonOnBean肯定不会生效,因为现在我的容器里面根本没有RedisTemplate.

    如果我先加载我们自定义的RedisTemplate,那我的ConditonOnBean就会判断,我的容器里面已经有我自己定义的RedisTemplate了,我自动配置的RedisTemplate你就不需要生效了.

4.2 分组特性

     如上图,我的selectImport导入的类如果进行排序,只会影响我当前分组的排序,不会影响容器里面其他Bean的排序,例如下图我的DeferredImportSelector.Group里面有一个RedisTemplate和,sqlSessionFactory,他们如果加了@Order注解去排序,只会在我这个组里面排序,不会影响其他.

5.最后附上一张自动配置原理的图

      总结:

      首先通过import导入一个DeferredImportSelector,因为保证顺序,他是最后加载的,为了保证我们自动配置类是放在最后,才能定制我们自己的类.他会去加载我们所有jar包当中的Spring.factories文件,并且获得里面配置的所有类名List,并且排序,过滤后返回给我们Spring,这样我们Spring就能管理这些Bean.也提现了Spring的扩展性,我们可以利用这个去写我们自己的starter,这种模式类似SPI.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值