SpringBoot2核心技术与响应式编程 (一)

接上文 《springboot 2 编程笔记
上篇文章我们详细解释了微服务核心概念
该篇主要讲解springboot的自动配置原理和实践

👉为了更好地理解SpringBoot内容,建议先掌握Maven工程相关知识。有了Maven基础后,学习SpringBoot会事半功倍。

文献参考:Maven整合Spring Boot 实战案例

  1. 基础入门
  2. 底层注解
  3. SpringBoot自动配置原理
    1. @SpringBootConfiguration
    2. @ComponentScan
    3. @EnableAutoConfiguration
      1. @AutoConfigurationPackage
      2. @Import({AutoConfigurationImportSelector.class})
    4. 给容器自动装入组件
    5. springboot 的配置绑定功能
  4. 实践
    1. SpringBoot的debug模式开启
    2. 查看、使用引入场景的配置项
    3. lombok
    4. 热部署插件
    5. SpringInitailizr

基础入门

spring 能做什么?

  • 微服务
  • 响应式编程
  • 分布式云开发
  • web开发
  • 无服务开发
  • 事件驱动
  • 批处理业务

SpringBoot优点

  • 创建独立Spring应用
  • 内嵌web服务器
  • 自动starter依赖,简化构建配置
  • 自动配置Spring以及第三方功能
  • 提供生产级别的监控、健康检查及外部化配置
  • 无代码生成、无需编写XML

创建SpringBoot2项目

我的idea2023默认只能创建springboot3工程,想创建springboot2项目需要些方法,在这里记录一下
点击Spring Initializr后替换为下面的服务器URL即可。

https://start.aliyun.com

在这里插入图片描述
创建web项目后,启动项目查看能否在浏览器上访问
在这里插入图片描述

如果我们想修改port端口号,可以将端口名添加到我们创建的 配置文件application.properties中,在pom文件中添加SpringBoot打包插件(可以把项目打成jar包,直接在目标服务器执行即可)

<!--SpringBoot打包插件-->
<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

在这里插入图片描述
在这里插入图片描述
修改后重启项目,在浏览器上查看是否生效
在这里插入图片描述

依赖管理机制

在软件开发过程中,模块化开发是常见做法。当不同开发者负责不同模块时,可能出现依赖版本不一致的问题。例如:

  • DAO模块开发者使用spring-context 5.1.9版本
  • Service模块开发者使用spring-context 5.2.0版本

当Service模块需要引用DAO模块,且这两个模块部署在同一个微服务中时,不同版本的spring-context jar包就会产生冲突。为了解决这个问题,我们需要通过依赖管理机制来统一各模块使用的依赖版本。
Maven项目通常采用聚合模块作为父项目,在父模块中统一管理spring-context等依赖的版本号,而dao和service模块则作为子模块继承父模块的配置。
springboot在做依赖管理时也是如此

我们在项目的pom文件中加上父引用

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-parent</artifactId>
    <version>2.3.4.RELEASE</version>
</parent>

其中的引用关系是:项目的父引用是spring-boot-parent,spring-boot-parent 的父项目是 spring-boot-dependencies ,spring-boot-dependencies 里面定义了开发过程中能遇到的所有的jar包的版本号
在这里插入图片描述

引用其他版本的依赖

如果你不认可SpringBoot版本仲裁中定义的MySQL,想使用MySQL的其他版本,你可以在项目的pom文件中添加配置,这样就可以覆盖spring-boot-dependencies 里面定义的MySQL版本号。
添加配置如下:
在这里插入图片描述

场景启动器

Spring Boot 启动器的命名格式为:spring-boot-starter-*.jar
只需引入特定场景的 starter,相关依赖就会自动被引入。

例如:我们引入spring-boot-starter-security场景启动器,只需在<dependencies> </dependencies>标签内添加依赖即可

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.7.18</version>
</dependency>

所有场景启动器的最底层的依赖都会依赖springboot自动配置的核心依赖
java <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <scope>compile</scope> </dependency>
注意:
引入的非版本仲裁的依赖需要写版本号

自定义场景启动器
我们可以根据业务需要制作特定的场景启动器,但为了区分SpringBoot官方的场景启动器在命名格式上需要注意:
格式:XXX-spring-boot-starter.jar

自动配置特性

  • 自动配置Tomcat
    • 引入Tomcat依赖
    • 配置Tomcat
      <dependency>
      	<groupId>org.springframework.boot</groupId>
      	<artifactId>spring-boot-starter-tomcat</artifactId>
      	<version>2.6.13</version>
      </dependency>
      
  • 自动配好SpringMVC
  • 自动配好Web常见功能,如:字符编码问题
    • SpringBoot帮我们配置好了所有web开发的常见场景
  • 默认的包结构
    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来。
    • 无需以前的包扫描配置
    • 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.study")
  • 各种配置拥有默认值
    • 默认配置最终都是映射到MultipartProperties
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象。
  • 按需加载所存自动配置项
    • 非常多的starter
    • 引入了哪些场景这个场景的自动配置才会开启

底层注解

@Configuration

告诉 springboot 这是一个配置类 == 配置文件(在 Spring 容器中默认是单实例)

原理解析:
Spring 的很多注解都是“复合注解”。@Configuration 本身是由 @Component 注解“修饰”的。你可以查看 @Configuration 的源码,其定义开头一定是这样的:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component	// 关键!这表明 @Configuration 本身也是一种特殊的 @Component
public @interface Configuration {

	@AliasFor(annotation = Component.class)
	String value() default "";

	boolean proxyBeanMethods() default true;

}

在上面代码中,因为 @Configuration 被 @Component 标注了,所以 @Configuration 注解的类也会被组件扫描(ComponentScan)发现,并注册为一个 Bean。它不仅仅是一个配置类,它本身也是 Spring 容器中的一个 Bean。

@AliasFor(annotation = Component.class)

  • @AliasFor 注解,字面意思就是“作为……的别名”。它用于在注解之间或同一注解内部声明属性的别名,从而实现元数据属性的覆盖或镜像
    在这段代码中:

    @AliasFor(annotation = Component.class) 非常明确地指出了:我(@Configuration 的 value 属性)是另一个注解(@Component)的 value 属性的别名。

    因为 @Configuration 的 value 属性是 @Component 的 value 属性的别名,所以你可以像使用 @Component 一样,使用 @Configuration 的 value 属性来为这个配置类本身指定一个在 Spring 容器中的 Bean 名称。

@configuration(proxyBeanMethods = false/true)

这个配置决定了 Spring 是否对该 @Configuration 配置类进行 CGLIB 代理增强。

  • proxyBeanMethods = true(默认值):Spring 会创建这个配置类的代理子类。从而拦截 @Bean 方法调用,保证 @Bean 方法返回的也是单例 Bean。
    原理图如下:
    在这里插入图片描述

  • proxyBeanMethods = false:Spring 不会创建代理。配置类就是一个普通类。每次调用它的 @Bean 方法,都会真正执行一次方法体,可能会产生新的实例。

@Bean

配置类里面使用@Bean注解在方法上给容器中添加组件。以方法名作为组件的id返回类型就是组件类型返回的值就是组件在容器中的实例(默认是单实例)

// 该注解可以标注在方法上或其他注解上
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
@Documented
public @interface Bean {
    // 与"name"属性互为别名。用于定义Bean的名称(ID)
    @AliasFor("name")
    String[] value() default {};

    // 与"value"属性互为别名。用于定义Bean的名称(ID)
    @AliasFor("value")
    String[] name() default {};

    // 【已过时】自动装配模式。现代Spring应用应使用@Autowired注解
    @Deprecated
    Autowire autowire() default Autowire.NO;

    // 是否作为自动装配的候选者(默认true)。设为false时,此Bean不会被@Autowired自动注入
    boolean autowireCandidate() default true;

    // 指定Bean初始化后调用的方法名
    String initMethod() default "";

    // 指定Bean销毁前调用的方法名。默认值INFER_METHOD会自动检测close或shutdown方法
    String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}

核心要点解析:

  1. Bean命名:
    value() 和 name() 功能完全相同,用于指定Bean在容器中的名称
    示例:
    @Bean(“myService”) 或 @Bean(name = “myService”)

  2. 自动装配控制:
    autowireCandidate 控制该Bean是否参与自动装配过程
    设为 false 时,只能通过名称显式获取,不会被 @Autowired 自动注入

  3. 生命周期方法:
    initMethod:Bean初始化后执行的方法
    destroyMethod:Bean销毁前执行的方法
    Spring会自动检测标准的销毁方法(如 close(), shutdown())

  4. 过时属性:
    autowire 已不推荐使用,应使用更现代的 @Autowired 注解

@Import 导入组件

@Import 注解的主要目的是将多个配置类或组件类合并到一个主配置类中。它允许你将配置分散到多个类中,然后通过导入的方式将它们组合起来,实现模块化和解耦
为了更好地理解 @Import 的工作原理,我们可以看一下Spring容器启动时如何处理它:
在这里插入图片描述

用法三:

  1. 导入普通的配置类(@Configuration 类)
    这是最直接的用法,用于将其他配置类引入到当前配置类中。
    当 Spring 处理 MyConfig 时,会同时处理 DatabaseConfig 和 TransactionConfig,相当于把三个配置类的内容合并了。
    在这里插入图片描述
    在这里插入图片描述
  2. 导入组件类(普通的 @Component 类)
    你可以直接导入任何被 @Component 标记的类(包括 @Service, @Repository, @Controller),Spring 会将其注册为 Bean。
    在这里插入图片描述

注意:这种方式要求被导入的类必须有默认的无参构造函数

  1. 导入特殊的选择器(高级用法)
    这是最强大的用法,允许你通过编程方式动态决定要导入哪些配置。

    • ImportSelector:根据条件动态选择要导入的配置类
    • ImportBeanDefinitionRegistrar:直接编程式注册Bean定义
    //创建一个环境配置类
    public class EnvBasedImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // 这里可以根据条件动态返回要导入的配置类
            if ("prod".equals(System.getenv("APP_ENV"))) {
                return new String[] {DataSourceConfig.class.getName()};
            } else {
                return new String[] {TransactionConfig.class.getName()};
            }
        }
    }
    

    在这里插入图片描述

@Conditional

@Conditional 是一个 Spring 注解,用于条件化地注册 Bean 或配置类。它的核心思想是:只有在满足特定条件时,才会将标注的组件(如 @Bean, @Component, @Configuration)注册到 Spring 容器中;如果不满足,则完全忽略它。

这为 Spring 应用带来了巨大的灵活性,允许你根据当前的环境、属性配置、类路径是否存在某个类、或其他任何自定义逻辑来决定是否启用某个配置。

原理解析:
@Conditional 的原理核心在于 Spring 容器在加载配置时的处理过程。

  1. 解析配置阶段:当 Spring 容器启动时,它会解析所有 @Configuration 类

  2. 收集 Bean 定义:对于每个带有 @Bean 的方法,Spring 会先创建一个 BeanDefinition 对象(Bean 的定义信息),但此时并不会立即实例化 Bean

  3. 条件评估(Condition Evaluation):

    • 对于每个被 @Conditional(或其派生注解)标注的 BeanDefinition,Spring 会调用其对应的 Condition 实现类的 matches(…) 方法
    • ConditionContext 参数提供了丰富的上下文信息,用于辅助判断:
      • BeanFactory:检查已存在的 Bean。
      • Environment:访问配置属性。
      • ResourceLoader:加载资源。
      • ClassLoader:检查类路径。
    • AnnotatedTypeMetadata 参数提供了正在被评估的注解的元数据,你可以从中获取注解的属性值。
  4. 注册或跳过:

    • 如果 matches 方法返回 true,那么这个 BeanDefinition 会被正式注册到容器中,后续会被实例化。
    • 如果返回 false,这个 BeanDefinition 会被直接丢弃,如同它从未存在过一样。

Spring Boot 预定义了大量基于 @Conditional 的“派生注解”,它们更语义化,使用起来也更方便。这些注解位于 org.springframework.boot.autoconfigure.condition 包下。

注解 目的 等效的 @Conditional 表达式
@ConditionalOnBean 当容器中存在指定 Bean 时 @Conditional(OnBeanCondition.class)
@ConditionalOnMissingBean 当容器中不存在指定 Bean 时 @Conditional(OnMissingBeanCondition.class)
@ConditionalOnClass 当类路径中存在指定类时 @Conditional(OnClassCondition.class)
@ConditionalOnMissingClass 当类路径中不存在指定类时 @Conditional(OnMissingClassCondition.class)
@ConditionalOnProperty 当指定的配置属性具有特定值时 @Conditional(OnPropertyCondition.class)
@ConditionalOnResource 当类路径中存在指定资源文件时 @Conditional(OnResourceCondition.class)
@ConditionalOnWebApplication 当应用是 Web 应用时 @Conditional(OnWebApplicationCondition.class)
@ConditionalOnNotWebApplication 当应用不是 Web 应用时 @Conditional(OnNotWebApplicationCondition.class)
@ConditionalOnExpression 当 SpEL 表达式为 true 时 @Conditional(OnExpressionCondition.class)
@ConditionalOnJava 当运行在指定的 Java 版本时 @Conditional(OnJavaCondition.class)
@ConditionalOnJndi 当指定的 JNDI 存在时 @Conditional(OnJndiCondition.class)
@ConditionalOnCloudPlatform 当指定的云平台被激活时 @Conditional(OnCloudPlatformCondition.class)

下面以@ConditionalOnProperty注解为例,演示其具体使用方法。

一、在配置文件中添加配置项

# 设置 true,MyFeatureService Bean 会被创建
myfeature.enabled=true

二、在配置类添加标签,在main方法添加判断Bean创建与否的代码

myConfig类中
/**
 *只有当配置文件中 `myfeature.enabled` 属性的值为 true 时,才创建这个 Bean
 *matchIfMissing = false 表示如果配置文件中根本没有这个属性,则视为不满足条件
*/
@Bean("serviceA")
@ConditionalOnProperty(name = "myfeature.enabled", havingValue = "true", matchIfMissing = false)
public ServiceA serviceA() {
	return new ServiceA(); // 方法体:创建 ServiceA 实例
}

main方法:
boolean serviceA = configurableApplicationContext.containsBean("serviceA");
System.out.println("容器中 serviceA 组件:"+serviceA);

结果如下:
在这里插入图片描述
如果 myfeature.enabled=false 或者 不写则不会创建ServiceA组件
在这里插入图片描述
在这里插入图片描述

@lmportResource 原生配置文件引入

@ImportResource 注解主要用于将传统的 XML 格式的 Spring 配置文件导入到基于注解的 Spring 应用程序中,实现新旧配置方式的混合使用

这里就不再演示

🤔 总结
@ImportResource 注解是一个有用的“桥梁”,它帮助我们在Spring Boot的注解世界中继续使用传统的XML配置。但对于新项目,强烈建议拥抱Spring Boot推荐的基于Java的配置方式,这样代码更类型安全、更简洁、也更容易重构。对于遗留项目,可以将其作为迁移过程中的辅助工具,同时逐步将XML配置现代化。

@ConfigurationProperties 配置绑定

@ConfigurationProperties 是一个 Spring Boot 注解,它的主要作用是:将配置文件(如 application.properties 或 application.yml)中的属性值,批量绑定到一个 Java 对象的字段上。

注意:
@ConfigurationProperties 注解后要添加 (prefix = "myapp.project)",以明确指定要读取的配置项来源。

@Component // 1. 将其声明为Spring容器管理的Bean
@ConfigurationProperties(prefix = "myapp.project") // 2. 指定配置的统一前缀
public class ProjectProperties {
	private String name;
   private String version;
   private String description;
   private List<String> authors;
   private Database database; // 嵌套属性
	
	// 必须提供Getter和Setter方法
	public Database getDatabase() { return database; }
   public void setDatabase(Database database) { this.database = database; }

   // 4. 静态内部类用于嵌套属性
   public static class Database {
       private String host;
       private Integer port;
       private Credentials credentials; // 再次嵌套

       // Getter and Setter ...
       public String getHost() { return host; }
       public void setHost(String host) { this.host = host; }
       public Integer getPort() { return port; }
       public void setPort(Integer port) { this.port = port; }
       public Credentials getCredentials() { return credentials; }
       public void setCredentials(Credentials credentials) { this.credentials = credentials; }

       public static class Credentials {
           private String username;
           private String password;
           // Getter and Setter ...
           public String getUsername() { return username; }
           public void setUsername(String username) { this.username = username; }
           public String getPassword() { return password; }
           public void setPassword(String password) { this.password = password; }
       }
   }

在yml文件里添加配置:

# application.yml
myapp:
 project:
   name: "My Awesome Project"
   version: "1.0.0"
   description: "This is a spring boot project"
   authors:
     - "Alice"
     - "Bob"
   database:
     host: "localhost"
     port: 3306
     credentials:
       username: "admin"
       password: "secret"

Bean会读取yml文件的配置项

简单来说,它让你能够:

  1. 集中管理配置:将所有相关的配置属性分组在一起。
  2. 类型安全:配置值会被自动转换为定义的 Java 类型(如 String, Integer, List, Map 等)。
  3. 避免样板代码:无需为每个配置属性使用 @Value(“${property}”) 注解。

一般有三种使用方法:

  1. 在类上使用 @ConfigurationProperties + @Component
    这种方式最简单直接,将配置类同时声明为一个 Spring 管理的组件(Bean)。
  2. 在类上使用 @ConfigurationProperties + 在配置类中使用 @EnableConfigurationProperties
    这种方式更显式,适合不希望用 @Component 扫描,或者需要第三方库的配置类。
  3. 在 @Bean 方法上使用 @ConfigurationProperties
    这种方式适用于为你无法直接修改源码的类绑定配置。
    @Configuration
    public class MyConfig {
    
        @Bean // 创建一个Bean
        @ConfigurationProperties(prefix = "myapp.project") // 在此处指定前缀
        public ProjectProperties projectProperties() {
            return new ProjectProperties(); // 返回一个空实例,属性由Spring注入
        }
    }
    

SpringBoot自动配置原理

在主启动程序上我们点开@SpringBootApplication配置类,它是由@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan三个注解组成


@SpringBootConfiguration

点击查看发现,该方法使用了@Configuration注解进行修饰,说明当前main方法属于一个配置类。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
	@AliasFor(
		annotation = Configuration.class
	)
	boolean proxyBeanMethods() default true;
}


@ComponentScan
指定扫描那些包


@EnableAutoConfiguration

观看学习视频时,有网友指出讲解内容存在偏差。我结合查阅的资料和个人理解作了修正说明。若仍有表述不够清晰之处,欢迎指正探讨。

自动配置的核心逻辑位于此处,深入查看可见它是一个复合注解,由@AutoConfigurationPackage@Import两个注解组合而成。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	Class<?>[] exclude() default {};

	String[] excludeName() default {};
}


@AutoConfigurationPackage(意为自动配置包)

该注解内部通过@Import实现功能:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})	//给容器中导入一个组件
public @interface AutoConfigurationPackage {
	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};
}

点击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));
 }

这段代码的核心目的就是:获取 Spring Boot 主启动类所在的包路径,并将其注册到 Spring 容器中,作为自动配置和组件扫描的“基准包”。(找到你的主启动类(比如 MyApplication)所在的包,然后告诉 Spring:“以后自动扫描和配置,就从这里开始!”)

我们打断点并debugger启动程序发现里面上传了2个参数 AnnotationMetadataBeanDefinitionRegistry
在这里插入图片描述
将这行核心代码拆解为几个步骤来理解:

  1. new PackageImports(metadata):

    • 这里的 metadata 参数包含了 @AutoConfigurationPackage 注解标注的类 的元信息,在标准的 Spring Boot 应用中,这通常就是你的主启动类标注了 @SpringBootApplication 的那个类(@SpringBootApplication包含@EnableAutoConfiguration
      包含@AutoConfigurationPackage包含@Import({AutoConfigurationPackages.Registrar.class})))。
    • PackageImports 是一个工具类,它的作用就是从 AnnotationMetadata解析出该类所在的包路径。例如,如果你的主启动类是 com.study.springboot2_01_helloworld.SpringBoot2_01_HelloWorldApplication,那么解析出的包名就是 com.study.springboot2_01_helloworld
  2. .getPackageNames()

    • 这个方法会返回一个包含包名的集合。虽然大多数情况下这里只包含一个包路径(主启动类所在的包),但设计上支持多个。
  3. .toArray(new String[0])

    • 这是一个常见的 Java 技巧,用于将集合转换为字符串数组。这里的 new String[0] 只是为了指定返回数组的类型。
  4. AutoConfigurationPackages.register(registry, ...)

    • 这是最关键的一步。它负责将解析得到的包路径数组注册到 Spring 的 BeanDefinitionRegistry(也就是 Spring 容器)中。
    • 注册时,Spring 会创建一个特殊的 Bean,其名称通常为 org.springframework.boot.autoconfigure.AutoConfigurationPackages,这个 Bean 就持有这些基准包路径
  5. 原理图如下:
    在这里插入图片描述

    🎯 主要作用与设计意图
    这段代码和 @AutoConfigurationPackage 注解的设计,主要有两个核心作用:
    为自动配置提供“基准包”
    Spring Boot 的自动配置机制需要知道从哪里开始扫描你的组件(如 @Component, @Service, @Repository 等)。通过将主启动类所在的包注册为“基准包”,Spring Boot 就能自动扫描该包及其所有子包下的组件,无需像传统 Spring 应用那样在 XML 或 Java 配置中手动指定 context:component-scan 或 @ComponentScan 的 basePackages。这极大地简化了配置,体现了 Spring Boot “约定优于配置” 的理念。
    供其他自动配置类使用
    一些第三方库的 Spring Boot 自动配置类(例如 Spring Data JPA、MyBatis 等)需要知道这个“基准包”路径,以便进行它们自己的扫描或配置。
    例如,MyBatis 的自动配置类 (MybatisAutoConfiguration) 可能会利用这个信息来自动扫描 Mapper 接口,而你不需要在 MyBatis 配置中手动指定所有 Mapper 的包路径。


@Import({AutoConfigurationImportSelector.class}) 给容器批量导入组件

Spring Boot 自动配置的核心入口,而 AutoConfigurationImportSelector 是这个机制中负责决策和加载的大脑。它的主要使命是告诉 Spring 容器:“应该自动配置哪些功能,以及按什么顺序配置”。工作流程如下图示:
在这里插入图片描述

点击查看AutoConfigurationImportSelector的核心代码:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);//给容器批量导入组件
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
         //  获取@EnableAutoConfiguration注解的属性(如exclude, excludeName)
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // 【核心步骤】获取到所有需要导入到容器中的配置类
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        // 移除重复的配置类(防止多个jar包声明了相同的配置)
        configurations = this.removeDuplicates(configurations);
        //获取用户明确指定要排除的配置类(通过exclude/excludeName属性)
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        // 检查要排除的类是否合法(是否在候选列表中)
        this.checkExcludedClasses(configurations, exclusions);
        // 从候选列表中移除所有被排除的类
        configurations.removeAll(exclusions);
        // 【最关键的过滤步骤】根据条件注解进行筛选
        configurations = this.getConfigurationClassFilter().filter(configurations);
        // 触发事件,通知所有监听器最终的自动配置列表
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        //  将最终确定的配置列表和排除列表封装返回
        return new AutoConfigurationEntry(configurations, exclusions);
    }
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	//这里调用了 `SpringFactoriesLoader` 类里面的 loadFactoryNames 方法
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

🔍 关键步骤详解:

  • 步骤 3: getCandidateConfigurations - 加载候选配置
    这是“发现”阶段。该方法通过 SpringFactoriesLoader 机制,从所有 Jar 包的 META-INF/spring.factories 文件中,读取 org.springframework.boot.autoconfigure.EnableAutoConfiguration key 对应的所有配置类的全限定名。(例如,spring-boot-autoconfigure jar 包下的 spring.factories 文件就包含了上百个自动配置类)

    • 在 spring-boot-autoconfigure jar 包中,spring.factories 文件包含大量自动配置类(具体数量因 Boot 版本而异,例如 2.6.13 版本包含 133 个配置项)。: 在这里插入图片描述

    org.springframework.boot:spring-boot-autoconfigure 这个 JAR 包中的 /META-INF/spring.factories 文件是自动配置类的清单。告诉 Spring Boot 有哪些“候选人”。是 Spring Boot 官方提供的、最全面的自动配置列表。它定义了所有 Spring Boot 内置支持的组件的自动配置类。

    查看 SpringFactoriesLoader 类源码,找到 loadFactoryNames方法:

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    	// 1. 确定要使用的类加载器(如果参数为空,则使用默认的)
    	ClassLoader classLoaderToUse = classLoader;
    	if (classLoader == null) {
    		classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    	}
    	// 2. 获取目标工厂接口/类的全限定名(例如:org.springframework.boot.autoconfigure.EnableAutoConfiguration)
    	String factoryTypeName = factoryType.getName();
    	// 3. 调用核心加载方法,并获取该类型对应的工厂实现类名称列表
    	return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    
    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    	// 1. 首先检查缓存,如果已经为这个ClassLoader加载过,直接返回缓存结果,提高性能
    	Map<String, List<String>> result = (Map)cache.get(classLoader);
    	if (result != null) {
    		return result;
    	} else {
    		// 2. 初始化一个空的结果Map,用于存放解析结果
    		Map<String, List<String>> result = new HashMap();
    
    		try {
    			// 3. 【关键步骤】查找类路径下所有的 META-INF/spring.factories 文件
          			// 注意:这里用的是 getResources(复数),不是 getResource
    			Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
    			// 4. 遍历找到的每一个 spring.factories 文件
    			while(urls.hasMoreElements()) {
    				URL url = (URL)urls.nextElement();
    				UrlResource resource = new UrlResource(url);
    				// 5. 将文件内容加载为Java Properties对象
    				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    				Iterator var6 = properties.entrySet().iterator();
    				// 6. 遍历Properties中的每一个键值对
    				while(var6.hasNext()) {
    					Map.Entry<?, ?> entry = (Map.Entry)var6.next();
    					String factoryTypeName = ((String)entry.getKey()).trim();
    					String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
    					// 7. 遍历实现类名称数组,将每个类名添加到结果Map对应的列表中
    					String[] var10 = factoryImplementationNames;
    					int var11 = factoryImplementationNames.length;
    
    					for(int var12 = 0; var12 < var11; ++var12) {
    						String factoryImplementationName = var10[var12];
    						//  computeIfAbsent: 如果Map中还没有这个factoryTypeName的键,就创建一个新ArrayList
                     			    //  然后无论新旧的List,都把当前的实现类名称加进去
    						((List)result.computeIfAbsent(factoryTypeName, (key) -> {
    							return new ArrayList();
    						})).add(factoryImplementationName.trim());
    					}
    				}
    			}
    			// 8. 对结果Map中的每一个List进行去重,并转换为不可修改的列表(保证安全)
    			result.replaceAll((factoryType, implementations) -> {
    				return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
    			});
    			// 9. 将最终结果放入缓存,以备下次使用
    			cache.put(classLoader, result);
    			return result;
    		} catch (IOException var14) {
    			throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
    		}
    	}
    }
    

    这段代码是 Spring Framework 中 SpringFactoriesLoader 类的核心方法,它实现了 Spring 工厂加载机制,这是 Spring Boot 自动配置的基石。它的作用是从类路径中所有 META-INF/spring.factories 文件中加载并解析出特定类型的工厂实现类

🌟 核心作用与设计思想总结

特性 说明 优点
全局扫描 使用 classLoader.getResources() 扫描整个类路径下所有 JAR 包中的 META-INF/spring.factories 文件。 可扩展性:任何第三方库都可以通过在自己 JAR 包中放置此文件来扩展 Spring。
统一约定 文件内容遵循 接口/抽象类全名=实现类全名1,实现类全名2,… 的 Properties 格式。 解耦:框架定义接口,实现由各个模块提供,双方不需要直接依赖。
缓存机制 使用静态 Map cache 以 ClassLoader 为键缓存加载结果。 高性能:避免每次都需要进行耗时的 I/O 扫描和解析。
多种工厂类型 一个文件中可以定义多种类型的工厂。不仅是自动配置,还用于初始化器、监听器等。 统一管理:一套机制服务多种扩展需求。

这一步之后,configurations 列表包含了所有“可能”被应用的自动配置类。

  • 步骤 8: getConfigurationClassFilter().filter(configurations) - 条件过滤
    这是“决策”阶段,也是整个自动配置过程最智能、最核心的一步。它根据一系列条件注解@Conditional...)来检查每个候选配置类是否真的应该被启用
    这些条件检查包括(但不限于):
    • @ConditionalOnClass:类路径下是否存在指定的类?
      例如,DataSourceAutoConfiguration 只有在类路径下存在 DataSource.class 时才会生效。
    • @ConditionalOnBean / @ConditionalOnMissingBean:容器中是否已经存在/缺少某个 Bean?
      • 这是实现“用户自定义配置优先”的关键。如果你自己定义了一个 DataSource Bean,那么 Spring Boot 提供的默认 DataSource 配置就会因为 @ConditionalOnMissingBean 条件不满足而跳过。
    • @ConditionalOnProperty:应用配置文件中某个属性是否有特定的值?
      • 例如,很多功能可以通过 application.properties 中的 spring.somefeature.enabled=true/false 来开关。
    • @ConditionalOnWebApplication / @ConditionalOnNotWebApplication:当前应用是否是 Web 应用?

🌟 核心作用与设计思想总结

Spring Boot 的自动配置遵循“约定优于配置”和“按需加载”的原则。虽然 spring.factories 中定义了上百个配置类,但并非全部生效。其核心机制是通过一系列 @Conditional 条件注解进行判断,最终只有满足当前应用环境和配置的类才会被激活。

这里以AopAutoConfiguration为例,剖析其决策过程:

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop",name = {"auto"},havingValue = "true",matchIfMissing = true)
public class AopAutoConfiguration {
	public AopAutoConfiguration() {}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass({"org.aspectj.weaver.Advice"})
	@ConditionalOnProperty(prefix = "spring.aop",name = {"proxy-target-class"},havingValue = "true",matchIfMissing = true)
	static class ClassProxyingConfiguration {
		ClassProxyingConfiguration() {}

		@Bean
		static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
			return (beanFactory) -> {
				if (beanFactory instanceof BeanDefinitionRegistry) {
					BeanDefinitionRegistry registry = (BeanDefinitionRegistry)beanFactory;
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
				}

			};
		}
	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({Advice.class})
	static class AspectJAutoProxyingConfiguration {
		AspectJAutoProxyingConfiguration() {}

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = true)
		@ConditionalOnProperty(prefix = "spring.aop",name = {"proxy-target-class"},havingValue = "true",matchIfMissing = true)
		static class CglibAutoProxyConfiguration {
			CglibAutoProxyConfiguration() {
			}
		}

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = false)
		@ConditionalOnProperty(prefix = "spring.aop",name = {"proxy-target-class"},havingValue = "false")
		static class JdkDynamicAutoProxyConfiguration {
			JdkDynamicAutoProxyConfiguration() {
			}
		}
	}
}
  1. 总开关确认
    AopAutoConfiguration 类首部通过 @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true) 注解,为整个自动配置机制提供了总开关控制。该注解会检查应用配置中是否存在 spring.aop.auto 属性,当其值为 true 时,该配置类才会激活。

    其中 matchIfMissing = true 是关键配置属性,表示当配置文件中未显式定义 spring.aop.auto 属性时,系统将默认视其为 true 值。这种设计确保了 AOP 自动配置的默认启用机制。

    因此,AopAutoConfiguration 默认处于生效状态。只有在配置文件中明确设置 spring.aop.auto=false 时,才会禁用该自动配置功能。

  2. 内部配置决策(按条件选择)

    • 在 AspectJAutoProxyingConfiguration 配置类上使用了 @ConditionalOnClass({Advice.class}) 条件注解。Advice 类来自 org.aspectj:aspectjweaver 这个依赖,但是我们的项目中并没有引入它。因此,这个条件不成立,导致 AspectJAutoProxyingConfiguration 配置类没有被激活(即‘没有生效’)。

    • 由于项目中没有引入 org.aspectj:aspectjweaver 依赖,无法满足 AspectJAutoProxyingConfiguration 配置类 @ConditionalOnClass({Advice.class}) 的条件,因此基于 AspectJ 的完整代理模式没有生效。

      然而,正是由于缺少这个依赖,反而满足了 ClassProxyingConfiguration 配置类 @ConditionalOnMissingClass({“org.aspectj.weaver.Advice”}) 的条件。结合其 @ConditionalOnProperty 条件的默认值(matchIfMissing = true),该配置类被成功激活。

      这相当于 Spring Boot 自动为我们 fallback 到了一个基础版的 AOP 自动配置。它确保了 Spring AOP 的核心功能(如 @Transactional 事务管理)依然可用,只是无法使用需要 AspectJ 支持的、更强大的切面编程功能。

经过这一步的筛选,configurations 列表中就只剩下了真正满足当前应用环境条件的、需要被启用的自动配置类。

我们debugger启动程序查看内容:

刚开始从spring factories文件中加载了全部的133项配置

在这里插入图片描述
经过getConfigurationClassFilter().filter(configurations)条件过滤后,真正被启用的只有28项
在这里插入图片描述
这一步之后,configurations 列表包含了所有真正被启用的自动配置类。


给容器自动装入组件

SpringBoot的自动装配流程此时才完成一半。接下来生效的配置类会向容器中注入大量组件,这些组件一旦存在于容器中,就意味着相应功能已具备。系统会优先使用用户自定义的组件,只有在用户没有配置时才会采用默认配置。

  1. HttpEncodingAutoConfiguration为例,这个类是 SpringBoot 用于自动化配置 HTTP 请求和响应字符编码的核心组件,我们通过源码解析来深入阐述其工作原理。

    @Configuration(proxyBeanMethods = false)	//标准配置类声明,使用 Lite 模式提升性能。
    @EnableConfigurationProperties({ServerProperties.class})	//核心配置绑定。启用对 ServerProperties 类的配置绑定。所有以 server. 为前缀的配置属性(如 server.servlet.encoding.charset, server.port)将被读取并注入到 ServerProperties Bean 中。注意,编码相关的配置是 ServerProperties 的一个嵌套属性。
    @ConditionalOnWebApplication(type = Type.SERVLET)	//此配置仅在基于 Servlet 的 Web 应用中生效。
    @ConditionalOnClass({CharacterEncodingFilter.class})	//条件检测,确保类路径下存在 Spring 的 CharacterEncodingFilter 类。这是字符编码过滤器的实现类。
    /**
     * prefix = "server.servlet.encoding", value = {"enabled"}: 检查配置项 server.servlet.encoding.enabled 的值。
     * matchIfMissing = true: 如果配置文件中未显式设置此属性,则默认视为 true。这意味着该自动配置默认是启用的,体现了 Spring Boot 的"开箱即用"理念。
     * 用户只有在显式设置为 false (server.servlet.encoding.enabled=false) 时才会禁用此自动配置。
     */
    @ConditionalOnProperty(prefix = "server.servlet.encoding", value = {"enabled"}, matchIfMissing = true)	// 总开关。这是最关键的条件注解:
    public class HttpEncodingAutoConfiguration {
    	
    	// 配置属性注入
        private final Encoding properties;
    
        public HttpEncodingAutoConfiguration(ServerProperties properties) {this.properties = properties.getServlet().getEncoding();}
    
    	// 核心 Bean 定义:字符编码过滤器
        @Bean
        @ConditionalOnMissingBean	// 关键条件:用户未自定义时生效
        public CharacterEncodingFilter characterEncodingFilter() {
            CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();	 // 1. 创建可排序的过滤器
            filter.setEncoding(this.properties.getCharset().name());	// 2. 设置编码 (e.g., UTF-8)
            filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.REQUEST));// 3. 是否强制请求编码
            filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.RESPONSE));// 4. 是否强制响应编码
            return filter;
        }
    
        @Bean
        public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {return new LocaleCharsetMappingsCustomizer(this.properties);}
    
        static class LocaleCharsetMappingsCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
            private final Encoding properties;
            LocaleCharsetMappingsCustomizer(Encoding properties) {
                this.properties = properties;
            }
            public void customize(ConfigurableServletWebServerFactory factory) {
                if (this.properties.getMapping() != null) {
                    factory.setLocaleCharsetMappings(this.properties.getMapping());
                }
            }
            public int getOrder() {
                return 0;
            }
        }
    }
    

    一、类级别注解分析
    == 图片1 ==
    在类的开头用了@EnableConfigurationProperties@ConditionalOnProperty 注解

    • @EnableConfigurationProperties({ServerProperties.class})
      核心配置绑定。启用对 ServerProperties 类的配置绑定。所有以 server. 为前缀的配置属性(如 server.servlet.encoding.charset, server.port)将被读取并注入到 ServerProperties Bean 中。注意,编码相关的配置是 ServerProperties 的一个嵌套属性
    • @ConditionalOnProperty(prefix = "server.servlet.encoding", value = {"enabled"}, matchIfMissing = true)
      总开关。这是最关键的条件注解:
      • prefix = “server.servlet.encoding”, value = {“enabled”}: 检查配置项 server.servlet.encoding.enabled 的值。

      • matchIfMissing = true: 如果配置文件中未显式设置此属性,则默认视为 true。这意味着该自动配置默认是启用的,体现了 Spring Boot 的"开箱即用"理念。用户只有在显式设置为 false (server.servlet.encoding.enabled=false) 时才会禁用此自动配置。

    二、配置属性注入
    == 图片2 ==

    • 通过构造器注入已经绑定好配置的 ServerProperties Bean。

    • 从中提取出嵌套的编码配置部分 (properties.getServlet().getEncoding()) 并存入字段 this.properties。这个 Encoding 对象包含了 charset, force, enabled, mapping 等具体配置值。

    三、核心 Bean 定义:字符编码过滤器
    == 图片3 ==
    这是本配置类最核心的方法。

    • @ConditionalOnMissingBean: 用户自定义优先原则的体现。只有在当前 Spring 容器中不存在任何 CharacterEncodingFilter 类型的 Bean 时,才会执行此方法创建默认的过滤器。如果用户已经自己定义了一个,那么这个自动配置就会跳过。

    • 创建与配置

      1. 实例化一个 OrderedCharacterEncodingFilter (是 CharacterEncodingFilter 的子类,支持排序)。

      2. setEncoding(...): 设置字符集。从配置中获取,例如 server.servlet.encoding.charset=UTF-8

      3. setForceRequestEncoding(...): 设置是否强制请求使用该编码。从配置中获取,例如 server.servlet.encoding.force-request=true

      4. setForceResponseEncoding(...): 设置是否强制响应使用该编码。从配置中获取,例如 server.servlet.encoding.force-response=true

    四、定制器 Bean:区域设置与字符集映射
    == 图片4 ==
    定制器 Bean:区域设置与字符集映射

    • 这个 Bean 的作用是进一步定制内嵌的 Servlet Web 服务器(如 Tomcat)。

    • 它实现了 WebServerFactoryCustomizer 接口,这是一个用于对已创建的 Web 服务器工厂进行后期加工的扩展点。

    • customize 方法: 如果配置中指定了 server.servlet.encoding.mapping 属性(这是一个 Map,例如 mapping.en=UTF-8, mapping.de=ISO-8859-1),它会将这些区域设置(Locale)与字符集(Charset)的映射关系设置到 Web 服务器工厂中。这为处理不同语言区域的请求提供了更精细的编码控制。

    • Ordered 接口: 指定该定制器的执行顺序。

    五、配置属性示例
    要使该自动配置生效,可以在 application.properties 中进行如下配置:

    # 启用编码过滤(默认true,可省略)
    server.servlet.encoding.enabled=true
    # 设置全局字符编码为 UTF-8
    server.servlet.encoding.charset=UTF-8
    # 强制请求使用配置的编码
    server.servlet.encoding.force-request=true
    # 强制响应使用配置的编码
    server.servlet.encoding.force-response=true
    # (可选) 设置特定区域设置的编码
    server.servlet.encoding.mapping.en=UTF-8
    server.servlet.encoding.mapping.de=ISO-8859-1
    

🌟 总结与设计思想
HttpEncodingAutoConfiguration 是一个典型的、功能完备的 Spring Boot 自动配置类,它展示了以下核心设计思想:

  • 条件化配置: 通过一系列 @ConditionalOn… 注解,确保只有在正确的应用类型和依赖条件下才生效。

  • 外部化配置: 通过 @EnableConfigurationProperties 与 ServerProperties 深度集成,将所有可配置选项暴露给用户。

  • 默认约定: 使用 matchIfMissing = true 提供合理的默认行为(默认启用 UTF-8 编码),实现"开箱即用"。

  • 用户自定义优先: 核心的 @ConditionalOnMissingBean 注解确保用户随时可以通过自己定义 CharacterEncodingFilter Bean 来完全覆盖此自动配置。

  • 扩展性: 通过 WebServerFactoryCustomizer 提供对底层 Web 服务器的低级定制能力,满足更复杂的需求。

最终效果是:开发者仅需引入 Web Starter,无需任何配置,即可获得一个配置好的、强制使用 UTF-8 编码的过滤器,有效解决中文乱码等常见问题。同时,又可以通过简单的配置文件修改或自定义 Bean 来精细控制其行为。


springboot 的配置绑定功能

有小伙伴就会有疑问了,我就写了一个简单的SpringBoot项目,也没有配什么端口号,怎么默认启动端口就是8080呢?
我记得我写SpringMVC的时候是要配端口号的啊,这就要讲一下SpringBoot的另一个重要概念:配置绑定功能
这里我们以MongoDB的配置类为例,深入解析其工作原理,先看 MongoProperties 源码

@ConfigurationProperties(prefix = "spring.data.mongodb")
public class MongoProperties {
    public static final int DEFAULT_PORT = 27017;
    public static final String DEFAULT_URI = "mongodb://localhost/test";
    private String host;
    private Integer port = null;
    private String uri;
    private String database;
    private String authenticationDatabase;
    private final Gridfs gridfs = new Gridfs();
    private String username;
    private char[] password;
    private String replicaSetName;
    private Class<?> fieldNamingStrategy;
    private UuidRepresentation uuidRepresentation;
    private Boolean autoIndexCreation;

    public MongoProperties() {
        this.uuidRepresentation = UuidRepresentation.JAVA_LEGACY;
    }

	//get、set方法
	这里省略...

    public String getUri() {
        return this.uri;
    }

    public String determineUri() {
        return this.uri != null ? this.uri : "mongodb://localhost/test";
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    public Integer getPort() {
        return this.port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public Gridfs getGridfs() {
        return this.gridfs;
    }

    public String getMongoClientDatabase() {
        return this.database != null ? this.database : (new ConnectionString(this.determineUri())).getDatabase();
    }

    public Boolean isAutoIndexCreation() {
        return this.autoIndexCreation;
    }

    public void setAutoIndexCreation(Boolean autoIndexCreation) {
        this.autoIndexCreation = autoIndexCreation;
    }

    public static class Gridfs {
        private String database;
        private String bucket;

        public Gridfs() {
        }

        public String getDatabase() {
            return this.database;
        }

        public void setDatabase(String database) {
            this.database = database;
        }

        public String getBucket() {
            return this.bucket;
        }

        public void setBucket(String bucket) {
            this.bucket = bucket;
        }
    }
}
  1. 类定义和注解
    在这里插入图片描述
    @ConfigurationProperties(prefix = "spring.data.mongodb") 注解表示这个类用于绑定配置文件中以 spring.data.mongodb 为前缀的属性

    这意味着你可以在 application.propertiesapplication.yml 中使用如 spring.data.mongodb.host=localhost 这样的配置

  2. 常量定义

    public static final int DEFAULT_PORT = 27017;
    public static final String DEFAULT_URI = "mongodb://localhost/test";
    

    定义了 MongoDB 的默认端口和默认连接 URI
    当没有明确配置时,会使用这些默认值

  3. 属性字段
    类中定义了多个属性字段,每个字段都对应一个配置选项:

    • host: MongoDB 服务器主机名
    • port: MongoDB 服务器端口
    • uri: MongoDB 连接 URI(如果设置了 URI,则会忽略单独的 host 和 port 设置)
    • database: 要连接的数据库名称
    • authenticationDatabase: 认证数据库
    • gridfs: GridFS 相关配置(嵌套配置类)
    • username: 连接用户名
    • password: 连接密码(使用 char[] 而不是 String,出于安全考虑)
    • replicaSetName: 副本集名称
    • fieldNamingStrategy: 字段命名策略类
    • uuidRepresentation: UUID 表示方式
    • autoIndexCreation: 是否自动创建索引
  4. 构造函数

    public MongoProperties() {
    	this.uuidRepresentation = UuidRepresentation.JAVA_LEGACY;
    }
    

    在构造函数中设置了 uuidRepresentation 的默认值为 JAVA_LEGACY

  5. 方法解析

    • 标准的 Getter 和 Setter 方法
      为每个属性字段提供了标准的 getter 和 setter 方法,是 SpringBoot 配置属性绑定所必需的
    • 特殊方法
      public String determineUri() {
      	return this.uri != null ? this.uri : "mongodb://localhost/test";
      }
      

    这个方法用于确定最终使用的 URI,如果明确配置了 URI,则使用配置的值;否则使用默认 URI

    public String getMongoClientDatabase() {
    	return this.database != null ? this.database : (new ConnectionString(this.determineUri())).getDatabase();
    }
    

    这个方法用于确定要连接的数据库。如果明确配置了数据库名称,则使用配置的值;否则从 URI 中解析出数据库名称

  6. 嵌套配置类 Gridfs

    public static class Gridfs {
     private String database;
     private String bucket;
     // ... getters and setters
    }
    

    这是一个嵌套的配置类,用于配置 GridFS 相关属性,可以配置 GridFS 的数据库和存储桶名称。对应配置如:spring.data.mongodb.gridfs.database=mygridfs

  7. 配置示例
    基于这个类,你可以在 application.properties 中进行如下配置:

    # 基本连接配置
    spring.data.mongodb.host=localhost
    spring.data.mongodb.port=27017
    spring.data.mongodb.database=mydatabase
    
    # 或者使用 URI 方式
    spring.data.mongodb.uri=mongodb://user:password@localhost:27017/mydatabase
    
    # 认证配置
    spring.data.mongodb.username=myuser
    spring.data.mongodb.password=mypassword
    spring.data.mongodb.authenticationDatabase=admin
    
    # GridFS 配置
    spring.data.mongodb.gridfs.database=fs
    spring.data.mongodb.gridfs.bucket=mybucket
    
    # 其他配置
    spring.data.mongodb.auto-index-creation=true
    

通过源码分析,我们了解到这个类是Spring Boot中用于MongoDB配置的属性类,它通过@ConfigurationProperties注解绑定配置文件中的属性。那么问题来了:这些配置文件究竟存放在哪里?是打包在jar文件中吗?

结论先行:这些属性不是写死的,它有配置文件,但这个“配置文件”指的是项目中的 application.propertiesapplication.yml 文件。 MongoProperties 类的作用就是作为一个桥梁,将这些外部配置文件中的属性值映射到Java对象的字段上

  1. 配置文件的来源和位置
    Spring Boot应用程序默认会加载名为 application.properties 或 application.yml 的配置文件

  2. @ConfigurationProperties 的作用:绑定
    MongoProperties 类本身不是一个配置文件,它是一个配置属性持有类(或称为配置元数据类)。它的工作流程如下:

    1. 编写配置:在 application.properties 中写入MongoDB的相关配置。
      spring.data.mongodb.host=my-mongo-server.com
      spring.data.mongodb.port=27017
      spring.data.mongodb.database=myappdb
      
    2. SpringBoot启动并扫描:应用程序启动时,SpringBoot会自动扫描所有标注了 @ConfigurationProperties 的类。
    3. 属性绑定(Magic Happens):SpringBoot 的配置绑定机制会:
      • 读取配置文件。
      • 根据 prefix = "spring.data.mongodb" 找到所有以这个为前缀的属性。
      • 截掉前缀,将剩下的部分(例如 host)与 MongoProperties 类中的字段名进行匹配。
      • 通过类的 setter 方法(如 setHost())将配置文件中字符串形式的的值(“my-mongo-server.com”),转换并注入到对应的字段中。
    4. 自动配置使用它:SpringBoot 的 MongoDB 自动配置类(通常是 MongoAutoConfiguration)会注入这个已经填充好值的 MongoProperties Bean,并用它来创建和配置真正的 MongoClient 连接对象。
  3. 配置类的默认值
    比如:private Integer port = null;他是字段的默认初始值。如果配置文件中没有设置 spring.data.mongodb.port端口号,那么这个字段的值就是 null。
    随后,在 determineUri() 这类方法中,程序会判断:“如果用户没配端口,就用默认的 27017”
    配置的优先级是:外部配置文件(最高)> Java代码中的默认初始值(最低)

为了更好地理解,我们可以做一个比喻,假设你在淘宝商城上的多家商店下单了多件商品,有鞋子,衣服,自行车等

  1. application.properties 文件:就像是一张订单,您在上面写下您的需求:MySQL的database叫什么、账密多少,Redis的服务器地址是多少、端口号多少,Activemq的账密多少、服务器地址多少
  2. XXXProperties 类:就像是一个个商家的标准订单表单,上面预先印好了各种可填的栏目,比如鞋子的码数、外观,衣服的款式、大小,自行车的配置和运输要求(host, port, database…)。@ConfigurationProperties 注解就是告诉淘宝商城(系统):“第一个表单是用来接 MySQL 订单的,第一个表单是接Redis的,最后一个表单是接Activemq的”。
  3. Spring Boot系统就像是淘宝商城,他拿你写的的订单,找到对应的各个商家的订单表单,把你填写的内容抄写到表单的对应栏目里,然后把这份填写好的表单交给各个商家(自动配置类)
  4. 自动配置类:各个商家根据淘宝商城发来的订单表单上填写的要求接单完成交易(创建MySQL连接、Redis链接、activemq连接)

因此,XXXProperties 类本身不包含任何写死的配置,它只是一个空的容器,等待外部配置文件( application.properties)来填充它
这种设计实现了完美的关注点分离:配置(做什么)和代码(怎么做)是分开的

实践

springboot debug模式

项目引入了场景依赖,如何判断哪些场景生效,哪些未生效?
启用SpringBoot的debug模式后,控制台会直接打印出生效和失效的场景信息。
在配置文件application.properties中添加配置:debugger=true
图片1
图片2

查看、使用引入场景的配置项

如果引入场景不符合当前业务需要,需要修改配置项,怎么找引入场景的配置项呢?
方法有三:

  1. 查看SpringBoot官方文档
    文档链接
  2. 直接在ide中搜索相关配置
    按 ctrl+N 输入 XXXproperties 查看XXX配置类,在@ConfigurationProperties标签内会有写有属性前缀,在applicantion.properties文件中进行相应的配置

我们以SecurityProperties为例
图片1
在application.properties文件中添加配置项
在这里插入图片描述
3. 可以在容器中放入一些自定义器来自定义一些规则
比如 XXXCustomizer,这里先埋个坑,后期我们会再讲怎么写自定义器
在这里插入图片描述

lombok

简化 JavaBean 的开发
在这里插入图片描述

  • @Data:生成getter、setter方法
  • @ToString:生成toString方法
  • @AllArgsConstructor:生成全参构造函数
  • @NoArgsConstructor:生成无参构造函数
    在这里插入图片描述

热部署插件

如果修改的是静态页就不需要重启

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
	<optional>true</optional>
</dependency>

Ctrl +F9: 项目重启

Spring Initailizr

需要什么场景就勾选什么场景
在这里插入图片描述
在这里插入图片描述

更多推荐