1、Boot骨架项目

厌倦了IDEA快速生成SpringBoot带来的各种无用文件?
image.png
Linux可以使用下面的方式,获取SpringBoot最简单的骨架项目(只包含一个pom.xml),也可以使用 浏览器,Postman 等工具

  1. curl -G https://start.spring.io/pom.xml -d dependencies=web,mysql,mybatis -o pom.xml

再使用如下命令,使用IDEA直接打开
image.png

2、Boot War项目

  1. 创建模块,区别在于打包方式选择 war

image-20211021160145072.png

  1. 接下来勾选 Spring Web 支持

image-20211021162416525.png

  1. 编写控制器

    @Controller
    public class MyController {
    
     @RequestMapping("/hello")
     public String abc() {
         System.out.println("进入了控制器");
         return "hello";
     }
    }
    
  2. 编写 jsp 视图,新建 webapp 目录和一个 hello.jsp 文件,注意文件名与控制器方法返回的视图逻辑名一致

    src
     |- main
         |- java
         |- resources
         |- webapp
             |- hello.jsp
    
  3. 配置视图路径,打开 application.properties 文件

    spring.mvc.view.prefix=/
    spring.mvc.view.suffix=.jsp
    

测试:

如果用 mvn 插件 mvn spring-boot:run 或 main 方法测试

  • 必须添加如下依赖,因为此时用的还是内嵌 tomcat,而内嵌 tomcat 默认不带 jasper(用来解析 jsp)

    <dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
    </dependency>
    
  • 也可以使用 Idea 配置 tomcat 来测试,此时用的是外置 tomcat

image.pngimage.png
image.pngimage.png
image.png

  • 注意:骨架生成的代码中,多了一个 ServletInitializer,它的作用就是配置外置 Tomcat 使用的,在外置 Tomcat 启动后,去调用它创建和运行 SpringApplication

3、Boot 启动过程

分为两个阶段,一个是执行 SpringApplication构造方法,一个是执行run 方法

阶段一:SpringApplication 构造方法

  1. 记录 BeanDefinition 源

    @Configuration
    public class A39_1 {
    
     public static void main(String[] args) {
         System.out.println("1. 演示获取 Bean Definition 源");
    
         SpringApplication spring = new SpringApplication(A39_1.class);
         // 添加来源
         spring.setSources(Collections.singleton("classpath:b01.xml"));
         // 创建和初始化spring容器
         ConfigurableApplicationContext context = spring.run(args);
    
         for (String name : context.getBeanDefinitionNames()) {
             System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
         }
    
         context.close();
    
     }
    
     static class Bean1 {
    
     }
    
     static class Bean2 {
    
     }
    
     static class Bean3 {
    
     }
    
     @Bean
     public Bean2 bean2() {
         return new Bean2();
     }
    
     @Bean
     public TomcatServletWebServerFactory servletWebServerFactory() {
         return new TomcatServletWebServerFactory();
     }
    }
    

    输出 ``java . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _ | \ \ \ \ \/ )| |)| | | | | || (| | ) ) ) ) ‘ |_| .|| ||| |\, | / / / / =========||==============|__/=///_/ :: Spring Boot :: (v2.5.4)

2022-05-27 02:08:33.109 INFO 24196 —- [ main] com.example.test39.A39_1 : Starting A39_1 using Java 1.8.0_281 on wumengxiaodeMBP with PID 24196 (/Users/shaw/java/spring_boot_test/target/classes started by shaw in /Users/shaw/java/spring_boot_test) 2022-05-27 02:08:33.112 INFO 24196 —- [ main] com.example.test39.A39_1 : No active profile set, falling back to default profiles: default 2022-05-27 02:08:33.476 INFO 24196 —- [ main] com.example.test39.A39_1 : Started A39_1 in 0.812 seconds (JVM running for 1.16) name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源:null name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源:null name: org.springframework.context.annotation.internalCommonAnnotationProcessor 来源:null name: org.springframework.context.event.internalEventListenerProcessor 来源:null name: org.springframework.context.event.internalEventListenerFactory 来源:null name: a39_1 来源:null name: bean1 来源:class path resource [b01.xml] name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null name: bean2 来源:com.example.test39.A39_1



2. 推断应用类型
```java
@Configuration
public class A39_1 {

    public static void main(String[] args) throws Exception {
        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        deduceFromClasspath.setAccessible(true);
        System.out.println("应用类型为:" + deduceFromClasspath.invoke(null));
    }
}

输出

应用类型为:SERVLET
  1. 记录 ApplicationContext 初始化器 ```java @Configuration public class A39_1 {

    public static void main(String[] args) throws Exception {

     SpringApplication spring = new SpringApplication(A39_1.class);
    
     System.out.println("3. 演示 ApplicationContext 初始化器");
     spring.addInitializers(configurableApplicationContext -> {
         if (configurableApplicationContext instanceof GenericApplicationContext) {
             GenericApplicationContext gac = (GenericApplicationContext) configurableApplicationContext;
             gac.registerBean("bean3", Bean3.class);
         }
     });
    
     // 创建和初始化spring容器
     ConfigurableApplicationContext context = spring.run(args);
    
    for (String name : context.getBeanDefinitionNames()) {
        System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
    }

    context.close();

}


static class Bean1 {

}

static class Bean2 {

}

static class Bean3 {

}

@Bean
public Bean2 bean2() {
    return new Bean2();
}

@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
    return new TomcatServletWebServerFactory();
}

}

输出
```java
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.4)

2022-05-27 02:40:55.061  INFO 24459 --- [           main] com.example.test39.A39_1                 : Starting A39_1 using Java 1.8.0_281 on wumengxiaodeMBP with PID 24459 (/Users/shaw/java/spring_boot_test/target/classes started by shaw in /Users/shaw/java/spring_boot_test)
2022-05-27 02:40:55.064  INFO 24459 --- [           main] com.example.test39.A39_1                 : No active profile set, falling back to default profiles: default
2022-05-27 02:40:55.480  INFO 24459 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-05-27 02:40:55.493  INFO 24459 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-05-27 02:40:55.493  INFO 24459 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.52]
2022-05-27 02:40:55.574  INFO 24459 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-05-27 02:40:55.574  INFO 24459 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 484 ms
2022-05-27 02:40:55.645  INFO 24459 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-05-27 02:40:55.653  INFO 24459 --- [           main] com.example.test39.A39_1                 : Started A39_1 in 1.062 seconds (JVM running for 1.567)
name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalCommonAnnotationProcessor 来源:null
name: org.springframework.context.event.internalEventListenerProcessor 来源:null
name: org.springframework.context.event.internalEventListenerFactory 来源:null
name: bean3 来源:null
name: a39_1 来源:null
name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null
name: bean2 来源:com.example.test39.A39_1
name: servletWebServerFactory 来源:com.example.test39.A39_1
  1. 记录监听器 ```java @Configuration public class A39_1 {

    public static void main(String[] args) throws Exception {

     SpringApplication spring = new SpringApplication(A39_1.class);
    
     System.out.println("4. 演示监听器与事件");
     spring.addListeners(event -> System.out.println("\t事件为:" + event.getClass()));
    
    // 创建和初始化spring容器
    ConfigurableApplicationContext context = spring.run(args);
    context.close();
}
输出
```java
4. 演示监听器与事件
    事件为:class org.springframework.boot.context.event.ApplicationStartingEvent
    事件为:class org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.4)

    事件为:class org.springframework.boot.context.event.ApplicationContextInitializedEvent
2022-05-27 02:44:36.823  INFO 24479 --- [           main] com.example.test39.A39_1                 : Starting A39_1 using Java 1.8.0_281 on wumengxiaodeMBP with PID 24479 (/Users/shaw/java/spring_boot_test/target/classes started by shaw in /Users/shaw/java/spring_boot_test)
2022-05-27 02:44:36.826  INFO 24479 --- [           main] com.example.test39.A39_1                 : No active profile set, falling back to default profiles: default
    事件为:class org.springframework.boot.context.event.ApplicationPreparedEvent
2022-05-27 02:44:37.225  INFO 24479 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-05-27 02:44:37.237  INFO 24479 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-05-27 02:44:37.237  INFO 24479 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.52]
2022-05-27 02:44:37.312  INFO 24479 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-05-27 02:44:37.312  INFO 24479 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 460 ms
2022-05-27 02:44:37.369  INFO 24479 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
    事件为:class org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
    事件为:class org.springframework.context.event.ContextRefreshedEvent
2022-05-27 02:44:37.375  INFO 24479 --- [           main] com.example.test39.A39_1                 : Started A39_1 in 0.989 seconds (JVM running for 1.343)
    事件为:class org.springframework.boot.context.event.ApplicationStartedEvent
    事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
    事件为:class org.springframework.boot.context.event.ApplicationReadyEvent
    事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
    事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
    事件为:class org.springframework.context.event.ContextClosedEvent
  1. 推断主启动类

    @Configuration
    public class A39_1 {
    
     public static void main(String[] args) throws Exception {
    
         SpringApplication spring = new SpringApplication(A39_1.class);
    
         System.out.println("5. 演示主类推断");
         Method deduceMainApplicationClass = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
         deduceMainApplicationClass.setAccessible(true);
         System.out.println("主类是:"+deduceMainApplicationClass.invoke(spring));
    
         // 创建和初始化spring容器
         ConfigurableApplicationContext context = spring.run(args);
         context.close();
     }
    }
    

    输出 ```java

  2. 演示主类推断 主类是:class com.example.test39.A39_1 ```

总结:
SpringApplication 构造方法中所做的操作
1. 可以有多种源用来加载 bean 定义
2. 应用类型推断
3. 容器初始化器
4. 演示启动各阶段事件
5. 演示主类推断

阶段二:执行 run 方法

  1. run方法首先要获得 SpringApplicationRunListeners(名字取得不好,实际是事件发布器 ),用来发布事件
    • SpringApplicationRunListener是个接口,具体实现类维护在了spring.factory文件中,可以通过SpringFactoriesLoader.loadFactoryNames来读取这个文件中的内容

image.png

  • 获得了SpringApplicationRunListener事件发布器后,通过 application starting 发布启动事件 ```java public class A39_2 {

    public static void main(String[] args) throws Exception { // 添加 app 监听器 SpringApplication app = new SpringApplication(); app.addListeners(e -> System.out.println(e.getClass()));

    // 获取事件发送器实现类名 List loadFactoryNames = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, A39_2.class.getClassLoader()); for (String name : loadFactoryNames) {

       System.out.println(name);
    
       // 拿到发布器的全路径后,通过反射获取
       Class<?> clazz = Class.forName(name);
       // 通过两个参数的构造器初始化
       Constructor<?> constructor = clazz.getConstructor(SpringApplication.class, String[].class);
       SpringApplicationRunListener publisher  = (SpringApplicationRunListener) constructor.newInstance(app, args);
    
       DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
       // 发布事件
       publisher.starting(bootstrapContext);
    

    } }

}


2.  封装启动 args 
```java
public class A39_3 {
    @SuppressWarnings("all")
    public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication();
        app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                System.out.println("执行初始化器增强...");
            }
        });

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
        DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);
    }
}
  1. 准备 Environment 添加命令行参数(*)

    public class Step3 {
    
    public static void main(String[] args) {
        AbstractEnvironment environment = new ApplicationEnvironment(); // 系统环境变量,将多个来源的配置信息整合到一起,比如properties,yml
    
        // 添加命令行来源,在spring中,命令行来源的优先级默认是最高的
        environment.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));
        // 添加配置文件来源,在spring中,配置文件来源优先级是最低的
        environment.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("application.properties")));
        // 获取env中包含的来源信息
        for (PropertySource<?> propertySource : environment.getPropertySources()) {
            System.out.println(propertySource.getName());
        }
    
        System.out.println(environment.getProperty("server.port"));
    }
    }
    

    添加命令行参数
    image.png
    添加配置文件参数
    image.png
    输出 ```java commandLineArgs systemProperties systemEnvironment class path resource [application.yml] 02:26:53.659 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key ‘server.port’ in PropertySource ‘commandLineArgs’ with value of type String 8070

Process finished with exit code 0


   - 输出的来源中,如果存在相同的配置信息,取值顺序按照从上到下的优先级

4.  ConfigurationPropertySources 处理(*) 
   - 发布 application environment 已准备事件
```java
public class Step4 {

    public static void main(String[] args) throws IOException {
        AbstractEnvironment env = new StandardServletEnvironment();

        env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("step4.properties")));
        // ConfigurationPropertySource.attch();
        System.out.println(env.getProperty("user.first-name"));
        System.out.println(env.getProperty("user.middle-name"));
        System.out.println(env.getProperty("user.last-name"));
    }
}

输出

02:36:54.741 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'user.first-name' in PropertySource 'class path resource [step4.properties]' with value of type String
George
null
null

通过添加ConfigurationPropertySource.attch();该方法会添加一个额外的来源,用来处理配置文件参数名格式问题。

  1. 通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)

    • application.properties,由 StandardConfigDataLocationResolver 解析
    • spring.application.json ```java public class Step5 { public static void main(String[] args) { SpringApplication app = new SpringApplication(); AbstractEnvironment env = new StandardServletEnvironment();

      System.out.println(“>>>>>>>>>>>>>>>>增强前”); for (PropertySource<?> propertySource : env.getPropertySources()) {

        System.out.println(propertySource.getName());
      

      } EnvironmentPostProcessor processor = new ConfigDataEnvironmentPostProcessor(new DeferredLogs(), new DefaultBootstrapContext()); EnvironmentPostProcessor processor1 = new RandomValuePropertySourceEnvironmentPostProcessor(new DeferredLog()); processor.postProcessEnvironment(env, app); processor1.postProcessEnvironment(env, app); System.out.println(“>>>>>>>>>>>>>>>>增强后”); for (PropertySource<?> propertySource : env.getPropertySources()) {

        System.out.println(propertySource.getName());
      

      } System.out.println(env.getProperty(“server.port”));

      System.out.println(env.getProperty(“random.int”)); }

}

输出
```java
>>>>>>>>>>>>>>>>增强前
systemProperties
systemEnvironment
>>>>>>>>>>>>>>>>增强后
systemProperties
systemEnvironment
random
Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'
Config resource 'class path resource [application.yml]' via location 'optional:classpath:/'
03:14:47.843 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'server.port' in PropertySource 'Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'' with value of type String
8090
03:14:47.843 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'random.int' in PropertySource 'random' with value of type Integer
195222868
  • spring通过EnvironmentPostProcessor环境后置处理器对env进行增强,进一步补充source,application.properties就是这一步处理的
  • spring将所有的后置处理器也定义到了spring.factories文件中

image.png

  • 上述 环境后置处理器的创建 是由EnvironmentPostProcessorApplicationListener事件监听器完成的

    public class Step5  {
      public static void main(String[] args) {
          SpringApplication app = new SpringApplication();
          AbstractEnvironment env = new StandardServletEnvironment();
          // 注册EnvironmentPostProcessorApplicationListener事件监听器
          app.addListeners(new EnvironmentPostProcessorApplicationListener());
    
          // 通过发送一个 环境准备完成事件,触发 EnvironmentPostProcessorApplicationListener事件监听器创建动作
          EventPublishingRunListener publish = new EventPublishingRunListener(app, args);
          publish.environmentPrepared(new DefaultBootstrapContext(), env);
    
      }
    
  1. 绑定 spring.main 到 SpringApplication 对象(*)

    • 先讲解一下@ConfigurationProperties的原理 ```java public class Step6 {

    public static void main(String[] args) throws IOException {

    SpringApplication app = new SpringApplication();
    AbstractEnvironment env = new StandardServletEnvironment();
    env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("step4.properties")));
    
    User user = Binder.get(env).bind("user", User.class).get();
    System.out.println(user);
    

    }

static class User {
    private String firstName;
    private String middleName;
    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getMiddleName() {
        return middleName;
    }

    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return "User{" +
            "firstName='" + firstName + '\'' +
            ", middleName='" + middleName + '\'' +
            ", lastName='" + lastName + '\'' +
            '}';
    }
}

}

输出
```java
User{firstName='George', middleName='Walker', lastName='Bush'}
  • 通过Binder.get(env).bind("user", User.class)可以将环境中的配置信息和Bean的属性进行绑定
  • 在配置文件维护springApplication的属性,可以将值绑定到springApplication对象中 ```java public static void main(String[] args) throws IOException { SpringApplication app = new SpringApplication(); AbstractEnvironment env = new StandardServletEnvironment(); env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource(“step6.properties”))); Binder.get(env).bind(“spring.main”, Bindable.ofInstance(app)); System.out.println(app); }

// 配置文件 spring.main.banner-mode=off spring.main.lazy-initialization=true

通过debug查看属性<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/466219/1653766756693-696d052d-3fe9-4554-ae03-10dc70d2e92c.png#clientId=u77194b76-a61d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=592&id=u8e7eea23&margin=%5Bobject%20Object%5D&name=image.png&originHeight=592&originWidth=763&originalType=binary&ratio=1&rotation=0&showTitle=false&size=121572&status=done&style=none&taskId=u869b15cc-de0d-4ae8-9550-a48fe96b7ab&title=&width=763)

7.  打印 banner(*) 

![image.png](https://cdn.nlark.com/yuque/0/2022/png/466219/1653767215386-e982a767-ad50-41a5-844f-85ce3bc9ec1d.png#clientId=u77194b76-a61d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=725&id=u6278c2bf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=725&originWidth=2103&originalType=binary&ratio=1&rotation=0&showTitle=false&size=643604&status=done&style=none&taskId=u4ca61a59-7581-46b1-b5ff-b874365369c&title=&width=2103)


8.  创建容器 
   - 根据构造方法中推断的应用类型,创建相应的容器
```java
public class A39_3 {
    @SuppressWarnings("all")
    public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication();
        app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                System.out.println("执行初始化器增强...");
            }
        });

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器");
        GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);

    }
}

private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
    GenericApplicationContext context = null;
    switch (type) {
        case SERVLET:
            return new AnnotationConfigServletWebServerApplicationContext();
        case REACTIVE:
            return new AnnotationConfigReactiveWebServerApplicationContext();
        case NONE:
            return new AnnotationConfigApplicationContext();
    }
    return context;
}
  1. 准备容器

    • 遍历之前添加的容器初始化器,回调初始化器的初始化方法,发布 application context 已初始化事件

      public class A39_3 {
      @SuppressWarnings("all")
      public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication();
        app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                System.out.println("执行初始化器增强...");
            }
        });
      
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器");
        GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);
      
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器");
        for (ApplicationContextInitializer initializer : app.getInitializers()) {
            initializer.initialize(context);
        }
      }
      }
      
  2. 加载 bean 定义

    • 从各种来源加载各种Bean定义,发布 application prepared 事件 ```java public class A39_3 { @SuppressWarnings(“all”) public static void main(String[] args) throws Exception { SpringApplication app = new SpringApplication(); app.addInitializers(new ApplicationContextInitializer() {

       @Override
       public void initialize(ConfigurableApplicationContext applicationContext) {
           System.out.println("执行初始化器增强...");
       }
      

      });

      System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args”); DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);

      System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器”); GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);

      System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器”); for (ApplicationContextInitializer initializer : app.getInitializers()) {

       initializer.initialize(context);
      

      }

      System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义”);

      DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory(); // 读取配置类来源的Bean定义 AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory); // 读取xml来源的Bean定义 XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory); // 扫描指定包下的Bean定义 ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);

      reader1.register(Config.class); reader2.loadBeanDefinitions(new ClassPathResource(“b03.xml”)); scanner.scan(“com.itheima.a39.sub”);

      for (String name : context.getBeanDefinitionNames()) {

       System.out.println("name:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
      

      } } }

@Configuration static class Config { @Bean public Bean5 bean5() { return new Bean5(); }

@Bean
public ServletWebServerFactory servletWebServerFactory() {
    return new TomcatServletWebServerFactory();
}

@Bean
public CommandLineRunner commandLineRunner() {
    return new CommandLineRunner() {
        @Override
        public void run(String... args) throws Exception {
            System.out.println("commandLineRunner()..." + Arrays.toString(args));
        }
    };
}   

}


11.  refresh 容器 
   - 发布 application started 事件
```java
context.refresh();
  1. 执行 runner

    • 发布 application ready 事件
    • 这其中有异常,发布 application failed 事件 ```java public class A39_3 { @SuppressWarnings(“all”) public static void main(String[] args) throws Exception { SpringApplication app = new SpringApplication(); app.addInitializers(new ApplicationContextInitializer() {

      @Override
      public void initialize(ConfigurableApplicationContext applicationContext) {
          System.out.println("执行初始化器增强...");
      }
      

      });

      System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args”); DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);

      System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器”); GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);

      System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器”); for (ApplicationContextInitializer initializer : app.getInitializers()) {

      initializer.initialize(context);
      

      }

      System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义”); DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory(); AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory); XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);

      reader1.register(Config.class); reader2.loadBeanDefinitions(new ClassPathResource(“b03.xml”)); scanner.scan(“com.itheima.a39.sub”);

      System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器”); context.refresh();

      for (String name : context.getBeanDefinitionNames()) {

      System.out.println("name:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
      

      }

      System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner”); for (CommandLineRunner runner : context.getBeansOfType(CommandLineRunner.class).values()) {

      runner.run(args);
      

      }

      for (ApplicationRunner runner : context.getBeansOfType(ApplicationRunner.class).values()) {

      runner.run(arguments);
      

      }

    } }

@Configuration static class Config { @Bean public Bean5 bean5() { return new Bean5(); }

@Bean
public ServletWebServerFactory servletWebServerFactory() {
    return new TomcatServletWebServerFactory();
}

@Bean
public CommandLineRunner commandLineRunner() {
    return new CommandLineRunner() {
        @Override
        public void run(String... args) throws Exception {
            System.out.println("commandLineRunner()..." + Arrays.toString(args));
        }
    };
}

@Bean
public ApplicationRunner applicationRunner() {
    return new ApplicationRunner() {
        @Override
        public void run(ApplicationArguments args) throws Exception {
            System.out.println("applicationRunner()..." + Arrays.toString(args.getSourceArgs()));
            System.out.println(args.getOptionNames());
            System.out.println(args.getOptionValues("server.port"));
            System.out.println(args.getNonOptionArgs());
        }
    };
}

}

添加运行参数<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/466219/1653760642688-2ff146de-98eb-4393-85fa-c984f9e60ce3.png#clientId=u77194b76-a61d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=126&id=u03bf2eb7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=126&originWidth=1103&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16544&status=done&style=none&taskId=u4c472e92-96bc-4d7c-aca6-581e5220b29&title=&width=1103)<br />输出
```java
>>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner
commandLineRunner()...[--server.port=8080, debug]
applicationRunner()...[--server.port=8080, debug]
[server.port]
[8080]
[debug]
  1. runner运行在容器启动完毕之后,可以在runner中添加自己的逻辑
  2. CommandLineRunner的参数一般是main方法的参数
  3. ApplicationRunner的参数需要经过封装,详情看第二步

4、Tomcat 内嵌容器

5、Boot 自动配置

演示 - 自动配置类原理

  • 假设已有第三方的两个自动配置类 ```java // 第三方的配置类1 @Configuration static class AutoConfiguration1 { @Bean public Bean1 bean1() {
      return new Bean1("第三方");
    
    } }

// 第三方的配置类2 @Configuration static class AutoConfiguration2 { @Bean public Bean2 bean2() { return new Bean2(); } }

static class Bean1 { private String name;

public Bean1(String name) {
    this.name = name;
}

@Override
public String toString() {
    return "Bean1{" +
        "name='" + name + '\'' +
        '}';
}

}

static class Bean2 {

}


- 本项目想要引入第三方的配置类,通过@Import导入第三方的配置类
```java
// 本项目配置类
@Configuration
@Import({AutoConfiguration1.class, AutoConfiguration2.class})
static class Config {

}
  • 通过ConfigurationClassPostProcessorBean工厂后置处理器,解析@Import注解 ```java public class A41_1 {

    public static void main(String[] args) {

      GenericApplicationContext context = new GenericApplicationContext();
      context.registerBean(Config.class);
      // 解析@Import,需要导入Bean工厂后处理器
      context.registerBean(ConfigurationClassPostProcessor.class);
    
      context.refresh();
    
      for (String name : context.getBeanDefinitionNames()) {
          System.out.println(name);
      }
    

    }

    // 本项目配置类 @Configuration @Import({AutoConfiguration1.class, AutoConfiguration2.class}) static class Config {

    }

    // 第三方的配置类1 @Configuration static class AutoConfiguration1 {

      @Bean
      public Bean1 bean1() {
          return new Bean1("第三方");
      }
    

    }

// 第三方的配置类2
@Configuration
static class AutoConfiguration2 {
    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }
}

static class Bean1 {
    private String name;

    public Bean1(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Bean1{" +
            "name='" + name + '\'' +
            '}';
    }
}

static class Bean2 {

}

}

输出
```java
com.example.test39.A41_1$Config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.example.test39.A41_1$AutoConfiguration1
bean1
com.example.test39.A41_1$AutoConfiguration2
bean
  • 实现@Import({AutoConfiguration1.class, AutoConfiguration2.class})中导入的类,通过配置文件的方式引入 ```java // 本项目配置类 @Configuration @Import(MyImportSelector.class) static class Config {

}

// 继承导入选择器,实现查询导入类的方法 static class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 加载spring.factories文件内容 return SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, MyImportSelector.class.getClassLoader()).toArray(new String[0]); } }


- 提供一个配置文件 META-INF/spring.factories,key 为导入器类名,值为多个自动配置类名,用逗号分隔
```xml
com.example.test39.A41_1$MyImportSelector=\
com.example.test39.A41_1.AutoConfiguration1,\
  com.example.test39.A41_1.AutoConfiguration2
  • 注意,SpringFactoriesLoader不仅会找当前项目中的spring.factories文件,还会找项目关联的jar中的spring.factories文件

image.png

  • 在本项目中添加一个和第三方bean名字相同的Bean,观察最终获取到的Bean ```java // 本项目配置类 @Configuration @Import(MyImportSelector.class) static class Config { @Bean public Bean1 bean1() {
      return new Bean1("本项目");
    
    } }

static class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, MyImportSelector.class.getClassLoader()).toArray(new String[0]); } }

// 第三方的配置类1 @Configuration static class AutoConfiguration1 { @Bean public Bean1 bean1() { return new Bean1(“第三方”); } }

// 第三方的配置类2 @Configuration static class AutoConfiguration2 { @Bean public Bean2 bean2() { return new Bean2(); } }

public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean(Config.class); // 解析@Import,需要导入Bean工厂后处理器 context.registerBean(ConfigurationClassPostProcessor.class);

context.refresh();

for (String name : context.getBeanDefinitionNames()) {
    System.out.println(name);
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(context.getBean("bean1"));

}


- 输出
```java
com.example.test39.A41_1$Config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.example.test39.A41_1$AutoConfiguration1
bean1
com.example.test39.A41_1$AutoConfiguration2
bean2
>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Bean1{name='本项目'}
  1. spring会先解析@Import注解内的Bean,再解析配置类中的Bean
  2. Bean工厂中后注册的Bean,会同名覆盖先注册的
  • 通过context.setAllowBeanDefinitionOverriding(false); 设置不允许覆盖,则会报错,springboot中就是设置的不允许覆盖

    Exception in thread "main" org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'bean1' defined in com.example.test39.A41_1$Config: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=com.example.test39.A41_1$Config; factoryMethodName=bean1; initMethodName=null; destroyMethodName=(inferred); defined in com.example.test39.A41_1$Config] for bean 'bean1': There is already [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=com.example.test39.A41_1$AutoConfiguration1; factoryMethodName=bean1; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/example/test39/A41_1$AutoConfiguration1.class]] bound.
    
  • 在日常开发中,本项目的优先级应该会高于第三方。可以使用DeferredImportSelector接口,调整解析的顺序,deferred即延迟导入

    static class MyImportSelector implements DeferredImportSelector {
      @Override
      public String[] selectImports(AnnotationMetadata annotationMetadata) {
          return SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, MyImportSelector.class.getClassLoader()).toArray(new String[0]);
      }
    }
    
  • 当本项目中没有导入时,才使用第三方的导入。可以使用@ConditionalOnMissingBean ,当Bean不存在时才加载

    // 第三方的配置类1
    @Configuration
    static class AutoConfiguration1 {
      @Bean
      @ConditionalOnMissingBean
      public Bean1 bean1() {
          return new Bean1("第三方");
      }
    }
    

总结:

  1. 自动配置类本质上就是一个配置类而已,只是用 META-INF/spring.factories 管理,与应用配置类解耦
  2. @Enable 打头的注解本质是利用了 @Import
  3. @Import 配合 DeferredImportSelector 即可实现导入,selectImports 方法的返回值即为要导入的配置类名
  4. DeferredImportSelector 的导入会在最后执行,为的是让其它配置优先解析

下面介绍一些典型的自动配置实现:

1、AopAutoConfiguration

  • 新建测试类,导入自动配置类相关的Bean

    public class TestAopAuto {
    
      public static void main(String[] args) {
          GenericApplicationContext context = new GenericApplicationContext();
          // 该工具类可以注册一些常用的=后置处理器
          AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
          context.registerBean(Config.class);
          context.refresh();
          for (String name : context.getBeanDefinitionNames()) {
              System.out.println(name);
          }
    
      }
    
      @Configuration
      @Import(MyImportSelector.class)
      static class Config {
    
      }
    
      static class MyImportSelector implements DeferredImportSelector {
          @Override
          public String[] selectImports(AnnotationMetadata importingClassMetadata) {
              // 导入自动配置类相关的Bean
              return new String[]{AopAutoConfiguration.class.getName()};
          }
      }
    }
    

    输出

    org.springframework.context.annotation.internalConfigurationAnnotationProcessor
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor
    org.springframework.context.annotation.internalCommonAnnotationProcessor
    org.springframework.context.event.internalEventListenerProcessor
    org.springframework.context.event.internalEventListenerFactory
    com.example.test39.TestAopAuto$Config
    // 下面这些是AopAutoConfiguration导入的Bean,internalAutoProxyCreator才是我们最终要的,剩下的三个其实是条件装配的中间过程
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration$CglibAutoProxyConfiguration
    org.springframework.aop.config.internalAutoProxyCreator
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
    
  • 查看AopAutoConfiguration源码,查看如何加载的Bean

  1. AopAutoConfiguration内部使用了条件判断来决定如何加载Bean

image.png

  • 如果设置spring.aop.auto=false ,就不会加载 ```java public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); StandardEnvironment env = new StandardEnvironment(); env.getPropertySources().addLast(new SimpleCommandLinePropertySource(“—spring.aop.auto=false”)); context.setEnvironment(env); // 该工具类可以注册一些常用的=后置处理器 AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory()); context.registerBean(Config.class); context.refresh(); for (String name : context.getBeanDefinitionNames()) {
      System.out.println(name);
    
    }

}

输出
```java
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
com.example.test39.TestAopAuto$Config
  1. 判断加载的类

image.png
image.png
image.png
image.png
image.png
总结:

  • AOP 自动配置类为 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
  • 可以通过 spring.aop.auto=false 禁用 aop 自动配置
  • AOP 自动配置的本质是通过 @EnableAspectJAutoProxy 来开启了自动代理,如果在引导类上自己添加了 @EnableAspectJAutoProxy 那么以自己添加的为准
  • @EnableAspectJAutoProxy 的本质是向容器中添加了 AnnotationAwareAspectJAutoProxyCreator 这个 bean 后处理器,它能够找到容器中所有切面,并为匹配切点的目标类创建代理,创建代理的工作一般是在 bean 的初始化阶段完成的

接下来的自动配置加载Bean的过程和上面类似,可以自己查看对应的规则:

public class TestDataSourceAuto {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        StandardEnvironment env = new StandardEnvironment();
        env.getPropertySources().addLast(new SimpleCommandLinePropertySource(
                "--spring.datasource.url=jdbc:mysql://localhost:3306/test",
                "--spring.datasource.username=root",
                "--spring.datasource.password=rootroot"
        ));
        context.setEnvironment(env);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.registerBean(Config.class);

        String packageName = TestDataSourceAuto.class.getPackage().getName();
        System.out.println("当前包名:" + packageName);
        AutoConfigurationPackages.register(context.getDefaultListableBeanFactory(),
                packageName);

        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            String resourceDescription = context.getBeanDefinition(name).getResourceDescription();
            if (resourceDescription != null)
                System.out.println(name + " 来源:" + resourceDescription);
        }
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config {

    }

    static class MyImportSelector implements DeferredImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{
                    DataSourceAutoConfiguration.class.getName(),
                    MybatisAutoConfiguration.class.getName(),
                    DataSourceTransactionManagerAutoConfiguration.class.getName(),
                    TransactionAutoConfiguration.class.getName()
            };
        }
    }
}

2、DataSourceAutoConfiguration

  • 对应的自动配置类为:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
  • 它内部采用了条件装配,通过检查容器的 bean,以及类路径下的 class,来决定该 @Bean 是否生效

简单说明一下,Spring Boot 支持两大类数据源:

  • EmbeddedDatabase - 内嵌数据库连接池
  • PooledDataSource - 非内嵌数据库连接池

PooledDataSource 又支持如下数据源:

  • hikari 提供的 HikariDataSource
  • tomcat-jdbc 提供的 DataSource
  • dbcp2 提供的 BasicDataSource
  • oracle 提供的 PoolDataSourceImpl

3、MybatisAutoConfiguration

  • MyBatis 自动配置类为 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
  • 它主要配置了两个 bean
    • SqlSessionFactory - MyBatis 核心对象,用来创建 SqlSession
    • SqlSessionTemplate - SqlSession 的实现,此实现会与当前线程绑定
    • 用 ImportBeanDefinitionRegistrar 的方式扫描所有标注了 @Mapper 注解的接口
    • 用 AutoConfigurationPackages 来确定扫描的包
  • 还有一个相关的 bean:MybatisProperties,它会读取配置文件中带 mybatis. 前缀的配置项进行定制配置

@MapperScan 注解的作用与 MybatisAutoConfiguration 类似,会注册 MapperScannerConfigurer 有如下区别

  • @MapperScan 扫描具体包(当然也可以配置关注哪个注解)
  • @MapperScan 如果不指定扫描具体包,则会把引导类范围内,所有接口当做 Mapper 接口
  • MybatisAutoConfiguration 关注的是所有标注 @Mapper 注解的接口,会忽略掉非 @Mapper 标注的接口

这里有同学有疑问,之前介绍的都是将具体类交给 Spring 管理,怎么到了 MyBatis 这儿,接口就可以被管理呢?

  • 其实并非将接口交给 Spring 管理,而是每个接口会对应一个 MapperFactoryBean,是后者被 Spring 所管理,接口只是作为 MapperFactoryBean 的一个属性来配置

4、TransactionAutoConfiguration

  • 事务自动配置类有两个:
    • org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
    • org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
  • 前者配置了 DataSourceTransactionManager 用来执行事务的提交、回滚操作
  • 后者功能上对标 @EnableTransactionManagement,包含以下三个 bean
    • BeanFactoryTransactionAttributeSourceAdvisor 事务切面类,包含通知和切点
    • TransactionInterceptor 事务通知类,由它在目标方法调用前后加入事务操作
    • AnnotationTransactionAttributeSource 会解析 @Transactional 及事务属性,也包含了切点功能
  • 如果自己配置了 DataSourceTransactionManager 或是在引导类加了 @EnableTransactionManagement,则以自己配置的为准

5、MVC自动配置类

ServletWebServerFactoryAutoConfiguration

  • 提供 ServletWebServerFactory

    DispatcherServletAutoConfiguration

  • 提供 DispatcherServlet

  • 提供 DispatcherServletRegistrationBean

    WebMvcAutoConfiguration

  • 配置 DispatcherServlet 的各项组件,提供的 bean 见过的有

    • 多项 HandlerMapping
    • 多项 HandlerAdapter
    • HandlerExceptionResolver

      ErrorMvcAutoConfiguration

  • 提供的 bean 有 BasicErrorController

    MultipartAutoConfiguration

  • 它提供了 org.springframework.web.multipart.support.StandardServletMultipartResolver

  • 该 bean 用来解析 multipart/form-data 格式的数据

    HttpEncodingAutoConfiguration

  • POST 请求参数如果有中文,无需特殊设置,这是因为 Spring Boot 已经配置了 org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter

  • 对应配置 server.servlet.encoding.charset=UTF-8,默认就是 UTF-8
  • 当然,它只影响非 json 格式的数据

6、自定义自动配置类

在演示-自动配置类原理的基础上,如何才能让springboot自动读取到我们自己的配置类呢?

  • 其实很简单,springboot也是通过@ImportImportSelector到配置文件中读取要加载的Bean,只不过它读取的key和我们不同

image.png
image.png
image.png
image.png
image.png

  • 所以,只要我们的key也配置成EnableAutoConfiguration,springboot就能加载我们的配置类了

    @SpringBootApplication
    public class App {
    
      public static void main(String[] args) {
          ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
          for (String name : context.getBeanDefinitionNames()) {
              System.out.println(name);
          }
      }
    }
    
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.example.test39.A41_1.AutoConfiguration1,\
    com.example.test39.A41_1.AutoConfiguration2
    

    输出
    image.png

6、条件装配底层

条件装配的底层是本质上是 @Conditional 与 Condition,这两个注解。引入自动配置类时,期望满足一定条件才能被 Spring 管理,不满足则不管理,怎么做呢?

比如条件是【类路径下必须有 dataSource】这个 bean ,怎么做呢?

  1. 首先编写条件判断类,它实现 Condition 接口,编写条件判断逻辑

    static class MyCondition1 implements Condition { 
     // 如果存在 Druid 依赖,条件成立
     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
         return ClassUtils.isPresent("com.alibaba.druid.pool.DruidDataSource", null);
     }
    }
    
  2. 其次,在要导入的自动配置类上添加 @Conditional(MyCondition1.class),将来此类被导入时就会做条件检查

    @Configuration // 第三方的配置类
    @Conditional(MyCondition1.class) // 加入条件
    static class AutoConfiguration1 {
     @Bean
     public Bean1 bean1() {
         return new Bean1();
     }
    }