条件注解

  1. @ConditionalOnClass:当指定的类在类路径上可用时,才创建 Bean

  2. @ConditionalOnMissingClass:当指定的类在类路径上不可用时,才创建 Bean

  3. @ConditionalOnProperty:指定的属性存在且具有特定的值时,才创建 Bean,注意 havingValue 才是指定属性值

    public @interface ConditionalOnProperty { 
    
     /**
      * Alias for { @link #name()} .
      * @return the names
      */
     String[] value() default { } ;
    
     /**
      * 配置文件的属性前缀
      */
     String prefix() default "";
    
     /**
      * 配置文件的属性名(key)
      */
     String[] name() default { } ;
    
     /**
      * 配置文件中的属性值(value)
      */
     String havingValue() default "";
    
     /**
      * 如果未设置属性,条件是否应匹配。默认为false
      */
     boolean matchIfMissing() default false;
    

}


4. **@ConditionalOnResource**:当指定的资源存在时,才创建 Bean
5. **@ConditionalOnExpression**:当指定的 SpEL(Spring Expression Language)表达式计算结果为 true 时,才创建 Bean
6. **@ConditionalOnJava**:当 Java 版本满足特定条件时,才创建 Bean
```java
@Bean  
@ConditionalOnJava(JavaVersion.EIGHT)  
public MyBean myBean() {   
    return new MyBean();  
} 

在 springboot 中是通过判断是否存在指定类且存在指定方法,从高到低去判断 java 版本的
image.png

  1. @ConditionalOnSingleCandidate:当指定的 Bean 在容器中只有一个候选者时,才创建 Bean
  2. @ConditionalOnWebApplication:当前项目为 web 项目的时候,才创建 Bean
  3. @ConditionalOnNotWebApplication:当前项目不为 web 项目的时候,才创建 Bean

配置文件

springboot 项目默认会使用 resources 目录下的 application.properties 作为配置文件,当然也可以直接使用注解指定,例如 springcloud 项目就会多指定一个 bootstrap.yaml 作为配置文件

@PropertySource("classpath:application.properties")

默认配置文件官方文档:
Core Features
优先级顺序:

  1. 命令行参数:命令行参数具有最高的优先级,可以覆盖其他任何配置文件中的设置
  2. 系统环境变量:系统环境变量在优先级上仅次于命令行参数,同样可以覆盖其他配置文件中的设置(虽然是读取环境变量的值,但是项目启动后,并不可以通过修改环境变量动态修改变量值)
  3. 配置文件:配置文件的优先级受到其类型位置的影响(在相同的位置和类型下,.properties格式的配置文件优先级高于.yaml或.yml格式的配置文件)

命令行参数 > 系统环境变量 > 外部指定配置文件 > jar包外部的特定配置文件 > jar包外部的普通配置文件 > jar包内部的指定配置文件 > jar包内部的普通配置文件 > 默认配置

位置影响:

  1. 内部配置文件位置优先级:config 目录下的配置文件优先级更高

image.png

  1. 在同目录下,指定环境的配置内容会优先于默认配置

image.png

  1. jar 包外部的配置文件优先级会高于内部,所以我们可以在 jar 包的同级目录下创建配置文件,从而覆盖默认的配置内容

image.png

  1. 外部目录 config 下的配置文件优先级更高

image.png

  1. 而在 config 目录下的任意目录,优先级更高

image.png

  1. 如果 config 目录下存在多个二级目录,则会取目录文件名排序较大的优先

image.png

  1. 但是如果我在二级目录 bbb 下又创建一个目录,则这个目录的配置文件不会被读取到

image.png
image.png

  1. 最后就是 .properties 配置文件优先级比 .yaml 优先级高

image.png

SPI机制

![image.png](spring.factories 文件,将 EnableAutoConfiguration 对应的所有类进行扫描,注册为配置类![image.png)

实现原理

1、在 @SpringBootApplication 注解中都会默认导入 @EnableAutoConfiguration 注解表示开启自动配置功能
image.png
2、查看自动配置类,其中导入了 AutoConfigurationImportSelector 类用于扫描依赖的配置文件
image.png
image.png
所以我们可以在配置文件中指定 spring.boot.enableautoconfiguration = false 用于关闭自动配置功能
image.png
4、而在 getAutoConfigurationEntry 方法中通过 getCandidateConfigurations 方法获取候选的自动配置类
image.png
5、getCandidateConfigurations 方法中指定加载 key 的名字为 EnableAutoConfiguration 的配置内容
image.png
6、在 loadFactoryNames 方法中通过 loadSpringFactories 方法将 META-INF/spring.factories 文件读取为一个 Map 对象,然后读取 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的内容
image.png
image.png
image.png

启动过程

1. 创建 SpringApplication 对象
2. 运行 run() 方法

image.png
创建 SpringApplication 对象

  1. 保存配置类 primarySources
  2. 推断 web 类型(判断是 REACTIVE 还是 SERVLET 或是为 NONE)
  3. 通过 SPI 获取 BootstrapRegistryInitializer、ApplicationContextInitializer、ApplicationListener 类型的 bean(getSpringFactoriesInstances() 方法同样也是去读取 META-INF/spring.factories 文件的内容)
  4. 推断主启动类

image.png

运行 run() 方法

  1. 获取运行监听器,即获取 spi 中的 SpringApplicationRunListener 的 bean,调用 starting() 方法发布 ApplicationStartingEvent 事件

  2. 获取运行参数和配置内容

  3. 打印 banner 内容

  4. 创建 spring 容器

  5. 准备容器内容(这一步在下面讲)

  6. 启动 spring 容器

  7. 循环调用运行监听器的 started() 方法,发布 ApplicationStartedEvent 事件

  8. 对运行参数的处理(这一步也在下面讲)

    public ConfigurableApplicationContext run(String... args) { 
     // 获取开始时间
     long startTime = System.nanoTime();
     DefaultBootstrapContext bootstrapContext = createBootstrapContext();
     ConfigurableApplicationContext context = null;
     configureHeadlessProperty();
    
     // 获取运行监听器,即获取 spi 中的 SpringApplicationRunListener 的 bean,并循环调用这些监听器的 starting() 方法
     SpringApplicationRunListeners listeners = getRunListeners(args);
     listeners.starting(bootstrapContext, this.mainApplicationClass);
     try { 
    
         // 获取运行参数和配置内容
         ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
         ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
         configureIgnoreBeanInfo(environment);
    
         // 打印 banner 内容
         Banner printedBanner = printBanner(environment);
    
         // 创建 spring 容器
         context = createApplicationContext();
         context.setApplicationStartup(this.applicationStartup);
    
         // 准备容器内容(这一步在下面讲)
         prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
         // 启动 spring 容器
         refreshContext(context);
         // 容器启动后处理,该方法为空,需要继承 SpringApplication 后扩展
         afterRefresh(context, applicationArguments);
         Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
         if (this.logStartupInfo) { 
             new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
         } 
    
         // 循环调用运行监听器的 started(),表示启动完成
         listeners.started(context, timeTakenToStartup);
    
         // 对运行参数的处理(这一步也在下面讲)
         callRunners(context, applicationArguments);
     } 
     catch (Throwable ex) { 
         handleRunFailure(context, ex, listeners);
         throw new IllegalStateException(ex);
     } 
     try { 
         Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
         listeners.ready(context, timeTakenToReady);
     } 
     catch (Throwable ex) { 
         handleRunFailure(context, ex, null);
         throw new IllegalStateException(ex);
     } 
     return context;
    } 

准备容器内容

  1. 调用 initialize() 方法

  2. 发布初始化事件 ApplicationContextInitializedEvent

  3. 获取启动时传入的配置类,并将其注册为 bean

  4. 发布初始化容器完成事件 ApplicationPreparedEvent

    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
         ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
         ApplicationArguments applicationArguments, Banner printedBanner) { 
     context.setEnvironment(environment);
     postProcessApplicationContext(context);
    
     // 调用初始化方法,获取 SPI 中的 ApplicationContextInitializer 的 bean,调用 initialize() 方法
     applyInitializers(context);
    
     // 发布初始化事件 ApplicationContextInitializedEvent
     listeners.contextPrepared(context);
     bootstrapContext.close(context);
     if (this.logStartupInfo) { 
         logStartupInfo(context.getParent() == null);
         logStartupProfileInfo(context);
     } 
     ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
     beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
     if (printedBanner != null) { 
         beanFactory.registerSingleton("springBootBanner", printedBanner);
     } 
     if (beanFactory instanceof AbstractAutowireCapableBeanFactory) { 
         ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
         if (beanFactory instanceof DefaultListableBeanFactory) { 
             ((DefaultListableBeanFactory) beanFactory)
                     .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
         } 
     } 
     if (this.lazyInitialization) { 
         context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
     } 
     context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
    
     // 获取启动时传入的配置类,并将其注册为 bean
     Set<Object> sources = getAllSources();
     Assert.notEmpty(sources, "Sources must not be empty");
     load(context, sources.toArray(new Object[0]));
    
     // 发布初始化容器完成事件 ApplicationPreparedEvent
     listeners.contextLoaded(context);
    } 

    调用初始化方法
    image.png
    将传入的配置类注册为 bean
    image.png

对运行参数的处理
这一步是获取 ApplicationRunner 和 CommandLineRunner 类型的所有 bean,并调用其 run() 方法,默认是不存在这两种类型的 bean 的,但是可以我们自己扩展

private void callRunners(ApplicationContext context, ApplicationArguments args) { 
    List<Object> runners = new ArrayList<>();

    // 获取 ApplicationRunner 和 CommandLineRunner 类型的所有 bean,并调用其 run() 方法
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) { 
        if (runner instanceof ApplicationRunner) { 
            callRunner((ApplicationRunner) runner, args);
        } 
        if (runner instanceof CommandLineRunner) { 
            callRunner((CommandLineRunner) runner, args);
        } 
    } 
} 

创建 ApplicationRunner 类型的 bean,并设置启动参数

@Component
public class MyApplicationRunner implements ApplicationRunner { 
    @Override
    public void run(ApplicationArguments args) throws Exception { 
        // 获取不符合格式的参数
        System.out.println(args.getNonOptionArgs());
        // 获取参数 key
        System.out.println(args.getOptionNames());
        // 获取指定参数的值
        System.out.println(args.getOptionValues("k1"));
        // 获取参数源内容
        for (String sourceArg : args.getSourceArgs()) { 
            System.out.print(sourceArg + " ");
        } 
    } 
} 

image.png
image.png

创建 CommandLineRunner 类型的 bean

@Component
public class MyCommandLineRunner implements CommandLineRunner { 
    @Override
    public void run(String... args) throws Exception { 
        for (String arg : args) { 
            System.out.print(arg + " ");
        } 
    } 
} 

image.png

容器初始化前配置

通过分析上面的启动过程,可知:
创建容器前:

  1. 通过监听 ApplicationStartingEvent 事件进行处理,不过通过以下方式并没有监听到(后续再看)

image.png
容器启动前:

  1. 可以创建 ApplicationContextInitializer 类型的 bean,会被自动调用 initialize() 方法

image.png
image.png

  1. 监听 ApplicationContextInitializedEvent 事件
  2. 监听 ApplicationPreparedEvent 事件

容器启动完成:

  1. 监听 ApplicationStartedEvent 事件

image.png

发现只有容器启动完成的 ApplicationStartedEvent 事件是可以监听到的
其他事件是通过 this.initialMulticaster.multicastEvent 发布的,不确定其监听方式,后续再看

后续来啦
其实并不是事件无法监听到,而是通过注解配置的监听器在 spring 容器创建前是不生效的(因为这些监听器还没有被扫描到并注册)
正确的使用方法如下:

  1. 创建 META-INF/spring.factories 配置文件,在这里指定监听器

    org.springframework.context.ApplicationListener=\
    cn.com.config.MyApplicationListener
  2. 创建监听器

    package cn.com.config;
    

import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.context.ApplicationListener;

public class MyApplicationListener
implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
System.out.println(“spring boot 启动”);
}
}


3. 启动项目,就会发现已经监听到了

![image.png](1712884965198-7b3be45d-0b37-4ad4-abdc-fece72ff7fe4.png)