Spring Boot

Stone大约 25 分钟

Spring Boot

Spring Boot 是一个开源的 Java 框架,用于简化 Spring 应用程序的初始搭建、开发、运行和部署过程。它使用“约定优于配置”(Convention Over Configuration)的理念,使开发者能够更快地创建和运行基于 Spring 的应用程序。Spring Boot 提供了许多开箱即用的功能,如自动配置、嵌入式服务器、健康检查、外部配置等,使得开发者能够专注于业务逻辑的实现,而无需过多关注底层配置和细节。

以下是 Spring Boot 的一些主要特点和优势:

  • 自动配置:Spring Boot 提供了大量的自动配置选项,能够自动配置应用程序所需的常见功能,如数据源、消息队列、安全性等。这大大减少了手动配置的工作量,提高了开发效率。
  • 嵌入式服务器:Spring Boot 内置了 Tomcat、Jetty 或 Undertow 等嵌入式服务器,使得开发者能够轻松地创建可独立运行的 Web 应用程序。这使得部署过程更加简单,无需依赖外部服务器。
  • 起步依赖:Spring Boot 为 Maven 和 Gradle 提供了许多插件和依赖项管理功能,使得项目的构建和依赖项管理更加简单。开发者只需在项目中添加相应的 Spring Boot 起步依赖项,即可快速构建和运行应用程序。
  • 健康检查:Spring Boot 提供了健康检查功能,可以监控应用程序的状态和性能。这有助于开发者及时发现和解决潜在的问题,确保应用程序的稳定性和可靠性。
  • 外部配置:Spring Boot 支持从多种来源(如命令行参数、环境变量、配置文件等)加载外部配置。这使得应用程序的配置更加灵活和可定制。
  • 快速开发:由于 Spring Boot 提供了许多开箱即用的功能和简化的配置过程,开发者能够更快地构建和部署应用程序。这使得快速开发和迭代成为可能,从而提高了开发效率。
  • 微服务支持:Spring Boot 是构建微服务架构的理想选择。它提供了许多与微服务相关的功能,如服务发现、负载均衡、容错处理等。这使得开发者能够更容易地构建、部署和管理微服务应用程序。

总之,Spring Boot 通过简化 Spring 应用程序的开发和部署过程,提高了开发效率和质量。它是 Java 开发者构建现代、高效、可靠的 Web 应用程序和微服务应用程序的强有力工具。

Spring Boot 3 是 Spring Boot 框架的一个重要更新版本,它延续了 Spring Boot 简化 Spring 应用程序开发的宗旨,并带来了一系列新特性和改进。以下是对 Spring Boot 3 的简要介绍:

  • 底层更新:Spring Boot 3 要求使用 Java 17 作为最低版本,以利用最新的语言特性和性能改进。此外,它还要求使用 Spring Framework 6.0.2 或更高版本,这个版本进行了一些特性的改进,包括 WebFlux 的增强、更好的性能和可扩展性。
  • 非功能特性改进:Spring Boot 3 进行了一些非功能特性的改进,例如对嵌入式服务器、安全、度量、健康检查和外部化配置等特性的支持。这些改进有助于开发者更方便地构建和管理应用程序。

学习 Spring Boot 3,需要提前掌握的内容有:

学习 Spring Boot 3,需要的环境:

起步

使用 Spring Boot 开发一个 Web 应用,浏览器发起 /hello 请求后,返回字符串 “Hello World”。

在 IDEA 中新建 Module,选择 Spring Initializr,JDK 及 Java 版本选择 17,Packaging 选择 Jar:

image-20240528105524521

依赖选择 Spring Web,然后点击 “Create”:

image-20240528105816212

然后点击 “Apply” 及 “Ok” 后,IDEA 开始创建该工程。

image-20240528111407007

创建完成后,会生成 pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>net.stonecoding</groupId>
    <artifactId>springboot-quickstart</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-quickstart</name>
    <description>springboot-quickstart</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

会在前面指定的包下创建启动类:

package net.stonecoding.springbootquickstart;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootQuickstartApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootQuickstartApplication.class, args);
    }

}

会在 resources 目录下创建配置文件 application.properties

spring.application.name=springboot-quickstart

创建 controller 包并创建 HelloController

package net.stonecoding.springbootquickstart.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String Hello() {
        return "Hello World";
    }
}

然后启动 Spring Boot 的启动类 SpringbootQuickstartApplication,使用浏览器访问地址 http://localhost:8080/hello 即可获取到响应的字符串 "Hello World"。

原理

依赖管理

从生成的 pom.xml 文件可以看到,使用 <parent> 标签指定父工程 spring-boot-starter-parent

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

spring-boot-starter-parent 中又指定了父工程 spring-boot-dependencies

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>3.3.0</version>
  </parent>

最终在 spring-boot-dependencies 中使用 <properties> 标签指定依赖版本,使用 <dependencyManagement> 标签指定依赖坐标。故此时在项目的 pom.xml 中引入相关依赖就无需手动指定版本号,而是会继承父工程中定义的版本。

虽然这个项目实现了 Spring MVC 的功能,但确认没有直接引入 Spring MVC 的依赖,而是引入了一个名称中包含 starter 的依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

这种依赖称为起步依赖。起步依赖本质上是一个 Maven 的依赖项,它并不包含具体的代码实现,而是对一组具有某种功能的依赖库的集合进行了定义。当我们在项目中引入一个起步依赖时,实际上是在引入一组预定义的、能够相互协作的依赖库,从而快速构建出满足某种功能需求的应用程序。

例如这里的起步依赖 spring-boot-starter-web 实际上就包含以下依赖:

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>3.3.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>3.3.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>3.3.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>6.1.8</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>6.1.8</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

启动类

在 Spring Boot 中,启动类是一个特殊的类,它使用 @SpringBootApplication 注解来标记,并包含了 main 方法,作为应用的入口点。

@SpringBootApplication
public class SpringbootQuickstartApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootQuickstartApplication.class, args);
    }

}

其中:

  • @SpringBootApplication 是一个组合注解,它包括了以下几个注解:
    • @SpringBootConfiguration:标记这个类是一个配置类,这告诉 Spring Boot 这是一个特殊的类,它包含了应用的一些配置信息。
    • @EnableAutoConfiguration:让 Spring Boot 根据类路径中的 JAR 包、其他 Bean 以及各种属性设置来自动配置应用。例如,如果 spring-webmvc 在类路径中,这个注解会自动配置成 Web 应用。
    • @ComponentScan:告诉 Spring Boot 在哪个包中查找组件、配置和服务,默认是启动类所在的包及其子包。如果在启动类所在的包外面,可以在启动类上使用该注解指定 @ComponentScan(basePackages = {"com.thirdparty", "your.own.packages"})
  • main 方法是标准的 Java 应用的入口点。在这个方法中,调用了 SpringApplication.run() 方法来启动 Spring Boot 应用。传递了 SpringbootQuickstartApplication.class 作为参数,这告诉 Spring Boot 应用的主配置类是什么。第二个参数是一个字符串数组,它通常包含了从命令行传递进来的参数。

注意:启动类的名称并不是固定的,可以根据你的项目需求来命名,并且位于根包下,这样 @ComponentScan 可以自动扫描到其他的组件。

注册 Bean

如果第三方库提供了 Spring Boot 的自动配置,那么可能只需要在 application.propertiesapplication.yml 中配置一些属性,Spring Boot 就会自动注册所需的 Bean。

如果第三方库没有提供自动配置,但它使用 Spring 的注解(如 @Component, @Service, @Repository, @Controller 等)来标记其组件,那么可以使用 @ComponentScan 注解来扫描这些组件并将它们注册为 Bean。可以在主配置类上添加 @ComponentScan 注解,并指定要扫描的包。

@SpringBootApplication  
@ComponentScan(basePackages = {"com.thirdparty", "your.own.packages"})  
public class MyApplication {  
    public static void main(String[] args) {  
        SpringApplication.run(MyApplication.class, args);  
    }
}

如果第三方库没有使用 Spring 的注解来标记其组件,或者需要更细粒度的控制 Bean 的创建,可以使用以下注解手动注册 Bean:

  • @Bean:用于定义单个 Bean。
  • @Import:用于导入其他配置类或组件类。

@Bean

当在配置类(使用 @Configuration 注解的类)中使用 @Bean 注解方法时,Spring 容器会调用该方法,将方法的返回值交给 IoC 容器,成为 IoC 容器的 Bean 对象,对象的名称默认为方法名。

@Configuration  
public class ThirdPartyConfiguration {  

    @Bean
    public ThirdPartyService thirdPartyService() {  
        return new ThirdPartyServiceImpl(); // 假设这是第三方库的服务实现  
    }  

    // 可以添加其他Bean定义...  
}

如果在方法的内部需要使用到 IoC 容器中已经存在的 Bean 对象,那么只需要在方法参数上声明即可,Spring 会自动注入:

@Configuration  
public class ThirdPartyConfiguration {  

    @Bean
    public ThirdPartyService thirdPartyService(OtherObject otherObject) {
        return new ThirdPartyServiceImpl(); // 假设这是第三方库的服务实现  
    }  

    // 可以添加其他Bean定义...  
}

还可以使用 @Value 注解为 Bean 的构造函数参数指定来自配置文件的值,并为其指定默认值:

@Configuration
public class ThirdPartyConfig {

    @Bean
    public ThirdPartyBean thirdPartyBean(@Value("${thirdparty.someProperty:defaultValue}") String someProperty) {
        return new ThirdPartyBean(someProperty);
    }
}

还可以使用条件注解根据特定条件来决定是否创建或配置 Bean,包括:

  • @ConditionalOnProperty:根据配置文件的属性值来决定是否加载或启用某个组件或配置类。
  • @ConditionalOnBean:根据 Spring 容器中是否存在某个 Bean 来决定是否加载配置或 Bean。
  • @ConditionalOnMissingBean:根据 Spring 容器中是否不存在某个 Bean 来决定是否加载配置或 Bean,常用于提供默认实现。
  • @ConditionalOnClass:根据 classpath 下是否存在某个类来决定是否加载配置或 Bean,常用于检查是否存在某个库或框架的依赖。
  • @ConditionalOnMissingClass:当类路径下不存在指定的类时,才会导入。
@ConditionalOnProperty

可以将 @ConditionalOnProperty 注解添加到 @Bean@Component@Configuration 或其他任何 Spring 管理的类上。注解有几个重要的属性:

  • name:要检查的属性的名称(默认为注解所在类或方法的名称,使用驼峰命名法转换为小写,并用点分隔)。
  • prefix:属性名称的前缀(当你有多个属性,它们共享相同的前缀时很有用)。
  • havingValue:期望的属性值(如果未指定,则属性只需存在即可)。
  • matchIfMissing:如果属性不存在,是否应包含 Bean(默认为 false)。
@Configuration  
public class MyFeatureConfig {  
  
    @Bean  
    @ConditionalOnProperty(name = "my.feature.enabled", havingValue = "true", matchIfMissing = false)  
    public MyFeatureService myFeatureService() {  
        return new MyFeatureService();  
    }  
}

在这个例子中,如果配置文件中 my.feature.enabled 属性设置为 true,则 MyFeatureService Bean 将被创建并注册到 Spring 容器中。如果属性不存在或设置为 false,则 Bean 将不会被创建。

@ConditionalOnBean

当 Spring 容器中存在指定类型的 Bean 时才会生效。被该注解标记的配置类或 Bean 只有在指定 Bean 存在时才会被加载进 Spring 容器。

@Configuration  
public class MyConfig {  
  
    @Bean  
    public MyService myService() {  
        return new MyService();  
    }  
  
    @Bean  
    @ConditionalOnBean(name = "myService")  
    public MyServiceHelper myServiceHelper() {  
        return new MyServiceHelper(myService());  
    }  
}

上面的例子中,myServiceHelper Bean 只有在 myService Bean 存在时才会被创建。

@ConditionalOnMissingBean

当 Spring 容器中不存在指定类型的 Bean 时才会生效。被该注解标记的配置类或 Bean 只有在指定 Bean 不存在时才会被加载进 Spring 容器。

@Configuration  
public class DefaultConfig {  
  
    @Bean  
    @ConditionalOnMissingBean  
    public DataSource dataSource() {  
        // 默认数据源实现  
        return new DefaultDataSource();  
    }  
}

在上面的例子中,如果没有在其他地方定义 DataSource Bean,则默认会使用这个 DefaultDataSource Bean。

@ConditionalOnClass

classpath 下有某个类时才装配。被该注解标记的配置类或 Bean 只有在指定的类存在于 classpath 下时才会被加载进 Spring 容器。

@Configuration  
@ConditionalOnClass(JdbcTemplate.class)  
public class JdbcConfig {  
  
    @Bean  
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {  
        return new JdbcTemplate(dataSource);  
    }  
}

在上面的例子中,如果 JdbcTemplate 类存在于 classpath 下(即开发者已经包含了 Spring JDBC 的依赖),则 JdbcConfig 配置类会被加载,并创建 JdbcTemplate Bean。

@Import

如果第三方库提供了一个配置类,可以使用 @Import 注解来导入这个配置类,这样其中的所有 Bean 定义都会被注册。

@SpringBootApplication  
@Import(ThirdPartyConfig.class) // 假设 ThirdPartyConfig 是第三方库提供的配置类  
public class MyApplication {  
    public static void main(String[] args) {  
        SpringApplication.run(MyApplication.class, args);  
    }  
}

当需要导入多个配置类时,为了简化启动类上的注解,可以实现 ImportSelector 接口,返回配置类名称数组:

// MyImportSelector.java
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{ConfigA.class.getName(), ConfigB.class.getName()};
    }
}

// MainConfig.java
@Configuration
@Import(MyImportSelector.class)
public class MainConfig {
    // other bean definitions
}

@SpringBootApplication
public class MyApplication {  
    public static void main(String[] args) {  
        SpringApplication.run(MyApplication.class, args);  
    }  
}

Spring Boot 会自动调用 selectImports 方法,得到配置类名称数组,将这些类中的 Bean 对象自动注入到 IoC 容器中。

在实际项目中,配置类名称一般是从后缀名为 .imports 的配置文件中读取,每个配置类名一行,例如:

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration

此时需要在实现 ImportSelector 接口时读取配置文件中的配置类名:

@Configuration
public class CustomImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 使用 MetadataReaderFactory 读取 .imports 文件中的类
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
        String[] importClassNames = new String[0];

        try {
            MetadataReader reader = metadataReaderFactory.getMetadataReader("path/to/your/config.imports");
            ClassMetadata classMetadata = reader.getClassMetadata();
            importClassNames = classMetadata.getClassNames();
        } catch (Exception e) {
            // Handle exceptions
        }

        return importClassNames;
    }
}

@Import 还可以与前面提到的条件注解一起使用,例如根据配置文件中的属性来决定是否导入一个配置类:

@Configuration
@ConditionalOnProperty(name = "myapp.feature.xenabled", havingValue = "true")
@Import(FeatureXConfig.class)
public class FeatureXAutoConfiguration {
    // ...
}

在这个例子中,只有当配置文件中 myapp.feature.xenabled 的值设置为 true 时,FeatureXConfig 类才会被导入。

还可以根据类路径上是否存在某个类来决定是否导入:

@Configuration
@ConditionalOnClass(com.example.SpecificClass.class)
@Import(SpecificClassConfig.class)
public class SpecificClassAutoConfiguration {
    // ...
}

在这个例子中,只有当 com.example.SpecificClass 这个类在类路径上时,SpecificClassConfig 类才会被导入。

通过结合使用 @Import 和条件注解,Spring Boot 能够提供灵活的配置选项,使得应用可以根据运行时的条件进行自我调整。这种机制是 Spring Boot 自动配置功能的基础,也是构建可扩展和模块化应用的关键。

自动配置

Spring Boot 的自动配置是 Spring Boot 框架的核心特性之一,旨在简化 Spring 应用的初始搭建以及开发过程。

其工作原理如下:

  • 在主启动类上添加了 @SpringBootApplication 注解,这个注解组合了 @EnableAutoConfiguration 注解。
  • @EnableAutoConfiguration 注解又组合了 Import 注解,导入了 AutoconfigurationImportSelector 类。
  • AutoconfigurationImportSelector 实现了 selectImports 方法,这个方法经过层层调用,最终会读取 META-INF 目录下后缀名为 .imports 的文件。在 Sprint Boot 2.7 版本之前,读取的是 spring.factories 文件。
  • 读取 .imports 文件中的全类名后,会解析该类上的注册条件注解,也就是 @Conditional 及其衍生注解,把满足条件的 Bean 对象自动注册到 IoC 容器中。

配置

Spring Boot 配置文件是项目中的关键组成部分,用于集中管理和修改应用程序的属性,而无需修改代码。只有添加了相应的依赖,才能进行对应的配置。所有的配置项可参考官方文档open in new window

一般将配置文件放在 resources 目录下,名称为 application.后缀名, 后缀名有两种格式:

  • Properties 格式:
    • 使用键值对的方式进行配置,例如:key=value
    • 通过 .properties 文件扩展名来标识。
    • 优点:简单、易于理解和书写,适用于简单的配置需求。
    • 缺点:缺乏层级结构的表示能力,对于复杂配置的可读性较差。
  • YAML 格式(也称为 YML):
    • 以层级结构的方式进行配置,使用缩进和冒号来定义层级关系。
    • 通过 .yml.yaml 文件扩展名来标识。
    • 优点:支持复杂的层级结构,配置更加清晰和易于阅读。
    • 缺点:语法规则可能对于初学者来说稍显复杂。

Spring Boot会自动从以下位置加载配置文件(按照优先级从高到低):

  • file:./config/
  • file:./
  • classpath:/config/
  • classpath:/

如果存在多个配置文件,Spring Boot 会按照以下顺序读取:

  • config/application.properties(项目根目录中 config 目录下)
  • config/application.yml
  • application.properties(项目根目录下)
  • application.yml
  • resources/config/application.properties(项目 resources 目录中 config 目录下)
  • resources/config/application.yml
  • resources/application.properties(项目的 resources 目录下)
  • resources/application.yml

注意:

  • 如果目录下同时存在 application.ymlapplication.properties,那么会先读取 application.properties
  • 如果同一个配置属性在多个配置文件中都配置了,那么会优先使用第一个读取到的配置,后面的配置不会覆盖前面的配置。

除了上述的 application.propertiesapplication.yml 之外,Spring Boot 还支持其他特殊用途的配置文件:

  • bootstrap.ymlbootstrap.properties:通常用于系统级别的配置,先于 application.ymlapplication.properties 加载。

在 Spring Boot 应用程序中,可以使用 @Value 注解或 @ConfigurationProperties 注解来读取配置文件中的属性值。

  • @Value:用于读取单个属性值。
  • @ConfigurationProperties:用于读取一组相关的配置属性,并将其映射到 Java 对象上。

当使用 java -jar 命令运行 Spring Boot 项目时,可以使用 --spring.config.location 命令行参数来指定配置文件的位置。例如:

java -jar your-app.jar --spring.config.location=file:/path/to/your/application.properties

也可以使用环境变量 SPRING_CONFIG_LOCATION 来设置 spring.config.location。例如:

export SPRING_CONFIG_LOCATION=file:/path/to/your/application.properties
java -jar your-app.jar

如果没有指定 --spring.config.locationSPRING_CONFIG_LOCATION,Spring Boot 将使用其默认的配置文件加载机制。默认情况下,它会按以下顺序加载配置文件(从最高优先级到最低优先级):

  • 当前目录下的 config 子目录
  • 当前目录
  • 类路径下的 config
  • 类路径的根目录

在这些位置中,它会查找名为 application.propertiesapplication.yml(或 application-{profile}.propertiesapplication-{profile}.yml,其中 {profile} 是当前激活的配置文件)的文件。

YAML

YAML(Yet Another Markup Language,又称 YAML Ain't Markup Language)是一种常用的数据序列化语言,用于所有编程语言中来表示像配置文件或数据文件之类的简单数据。它的设计目标是易于人类阅读、编写和机器解析。YAML 的语法简洁明了,采用缩进和层次结构来表示数据,这使得它非常适合于配置文件。

特点:

  • 易读性:YAML 的语法清晰简洁,采用缩进和冒号(:)来表示数据之间的层次关系,这使得它非常易于人类阅读和理解。
  • 易于编写:由于其简单的语法规则,YAML 也很容易编写。开发人员可以快速地创建和修改 YAML 文件。
  • 跨语言:YAML 是一种跨平台、跨语言的数据序列化标准,可以被多种编程语言解析和使用。
  • 可扩展性:YAML 提供了丰富的数据类型,包括字符串、数字、布尔值、列表(数组)、字典(哈希表/映射)等,并且支持自定义数据类型。
  • 注释:YAML 支持注释,这对于编写配置文件时添加说明和解释非常有用。

语法:

  • 大小写敏感:YAML 对大小写敏感,如 trueTrueTRUE 会被视为不同的值。

  • 缩进:使用空格进行缩进,通常是两个空格,不允许使用制表符。使用缩进来表示数据的层次结构。同一层次的数据使用相同的缩进量。

  • 序列:用短横线(-)表示列表项。每个列表项占据一行,并且具有相同的缩进量。

  • 映射:用冒号(:)分隔键和值来表示键值对,值前面添加空格(键与值之间使用冒号加空格作为分隔)

  • 注释:以井号(#)开头的行是注释。

  • 文档分隔符:YAML 文件以三个连续的短横线(---)开头,表示一个新的文档开始。

示例:

# 这是一个注释
person:
  name: John Doe
  age: 30
  interests:
    - Reading
    - Hiking
    - Coding
isEmployed: true

在这个示例中,person 是一个映射,它包含键值对,其中 nameageisEmployed 是标量,而 interests 是一个序列。

读取配置

可以使用以下几种方式读取配置文件中的数据。

@Value

使用 @Value("表达式") 注解可以从配置文件中获取单个属性值,注解中使用 SpEL 表达式读取属性:${一级属性名.二级属性名……}

当使用 @Value 注解引用属性时,可以在属性名称后面使用冒号(:default-value)的形式添加默认值。这样,如果在配置文件中找不到对应的属性,就会使用默认值。如果在配置文件中找到了属性,其值将会覆盖默认值。

@Value 注解只能用于被 Spring 管理的 Bean 中使用,如使用 @Component@Service@Controller 等注解修饰的类,或者使用 Java 配置编写的 @Configuration 类中。

@Value 注解可以用于字段、构造函数参数、方法参数和方法上。当将它放在方法上时,Spring 容器初始化时会调用该方法,并将配置属性的值作为方法的参数传递进去。

@Value 注解不能在 static 修饰的字段上使用。因为 @Value 注解是通过访问 Spring 容器中的上下文来解析属性值并注入到目标字段中的。由于 static 字段不属于对象实例,无法通过实例访问容器,所以在静态字段上使用 @Value 注解是无效的。

例如对于以下配置:

lesson: SpringBoot

server:
  port: 80

enterprise:
  name: itcast
  age: 16
  tel: 4006184000
  subject:
    - Java
    - 前端
    - 大数据

使用 @Value 注解读取配置文件数据如下:

@RestController
@RequestMapping("/books")
public class BookController {
    
    @Value("${lesson}")
    private String lesson;
    @Value("${server.port}")
    private Integer port;
    @Value("${enterprise.subject[0]}")
    private String subject_00;

    @GetMapping("/{id}")
    public String getById(@PathVariable Integer id){
        System.out.println(lesson);
        System.out.println(port);
        System.out.println(subject_00);
        return "hello , spring boot!";
    }
}

@ConfigurationProperties

在 Spring Boot 中,@ConfigurationProperties 是一个非常有用的注解,它可以用来绑定配置文件中前缀为特定值的属性到配置类中的字段。

使用 @ConfigurationProperties 的基本步骤:

  • 定义配置属性前缀:在配置文件中定义一组具有共同前缀的属性。
myapp:  
  name: My App  
  description: This is my awesome app!  
  version: 1.0.0
  • 创建配置类:创建一个配置类,并使用 @ConfigurationProperties 注解来指定前缀。确保类中的字段与属性名匹配(驼峰命名)。并在类上添加 @Component 注解以交给 Spring 管理。
import org.springframework.boot.context.properties.ConfigurationProperties;  
import org.springframework.stereotype.Component;  
  
@Component  
@ConfigurationProperties(prefix = "myapp")  
public class MyAppProperties {  
  
    private String name;  
    private String description;  
    private String version;  
  
    // 标准的getter和setter方法  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public String getDescription() {  
        return description;  
    }  
  
    public void setDescription(String description) {  
        this.description = description;  
    }  
  
    public String getVersion() {  
        return version;  
    }  
  
    public void setVersion(String version) {  
        this.version = version;  
    }  
}

出现 “Spring Boot Configuration Annotation Processor not configured” 告警,在 pom.xml 中添加以下依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
  • 注入配置类:现在就可以在其他 Spring 组件中注入 MyAppProperties 类,并使用其中的配置了。
@RestController
public class HelloController {

    @Autowired
    private MyAppProperties myAppProperties;

    @RequestMapping("/hello")
    public String Hello() {
        System.out.println("Name: " + myAppProperties.getName());
        System.out.println("Description: " + myAppProperties.getDescription());
        System.out.println("Version: " + myAppProperties.getVersion());
        return "Hello World";
    }
}

Environment

Spring Boot 还可以使用 @Autowired 注解注入 Environment 对象的方式读取数据。这种方式 Spring Boot 会将配置文件中所有的数据封装到 Environment 对象中,如果需要使用哪个数据只需要通过调用 Environment 对象的 getProperty(String name) 方法获取。

@RestController
public class HelloController {

    @Autowired
    private Environment env;

    @RequestMapping("/hello")
    public String Hello() {
        System.out.println(env.getProperty("lesson"));
        System.out.println(env.getProperty("enterprise.name"));
        System.out.println(env.getProperty("enterprise.subject[0]"));
        return "Hello World";
    }
}

一旦有了 Environment 对象的实例,就可以使用它来获取配置信息:

  • 获取单个属性值:使用 getProperty(String key) 方法。

    String appName = env.getProperty("app.name");
    
  • 获取属性值并转换为特定类型:使用 getPropertyValue(String key, Class<T> targetType) 方法。

    int timeout = env.getProperty("app.timeout", int.class);
    
  • 获取默认值:如果属性不存在,可以提供一个默认值。

    String appName = env.getProperty("app.name", "Default Application Name");
    
  • 获取命令行参数:使用 getCommandLineArgs() 方法。

    String[] args = env.getCommandLineArgs();
    
  • 获取活动配置文件:使用 getActiveProfiles() 方法。

    String[] activeProfiles = env.getActiveProfiles();
    
  • 检查活动配置文件:使用 profiles 方法。

    boolean isDevProfileActive = env.getActiveProfiles() != null && Arrays.asList(env.getActiveProfiles()).contains("dev");
    

多环境

在 Spring Boot 中,多环境配置是为了支持不同的部署环境(如开发、测试、生产等),每个环境可能需要不同的配置设置,如数据库连接、服务器端口、日志级别等。

  • 命名规则:Spring Boot 使用 application-{profile}.propertiesapplication-{profile}.yml 来定义特定环境的配置,其中 {profile} 是环境的名称。
  • 示例:
    • 开发环境:application-dev.propertiesapplication-dev.yml
    • 测试环境:application-test.propertiesapplication-test.yml
    • 生产环境:application-prod.propertiesapplication-prod.yml

在这些文件中,可以定义特定于该环境的配置。例如,在 application-dev.yml 中,可能会有如下配置:

server:  
  port: 8081  
spring:  
  datasource:  
    url: jdbc:mysql://localhost:3306/dev_db  
    username: dev_user  
    password: dev_password

通常,还需要一个 application.propertiesapplication.yml 文件,它包含所有环境的通用配置,并作为默认配置。这个文件不需要指定环境标识。然后使用 spring.profiles.active 属性来指定激活哪个环境的配置。

spring:  
  profiles:  
    active: dev # 这将激活application-dev.yml中的配置

当启动 Spring Boot 应用时,还可以通过命令行参数来指定激活哪个环境的配置:

java -jar your-app.jar --spring.profiles.active=dev

同时也可以在命令行指定其他配置:

java –jar your-app.jar –-server.port=8081 –-spring.profiles.active=test

整合

整合 MyBatis

MyBatisopen in new window 是一个功能强大、灵活且易于使用的持久层框架,它可以帮助开发者更加高效地进行数据库操作,提高开发效率和质量。

在 IDEA 中新建 Module,选择 Spring Initializr,JDK 及 Java 版本选择 17,Packaging 选择 Jar:

image-20240603150900865

依赖选择 MyBatis Framework 和 MySQL Driver,然后点击 “Create”:

image-20240603151130300

domain 包下创建实体类:

public class Book {
    private Integer id;
    private String name;
    private String type;
    private String description;
    
    //setter and  getter
    
    //toString
}

dao 包下创建 Dao 接口,并在接口上添加 @Mapper 注解:

@Mapper
public interface BookDao {
    @Select("select * from tbl_book where id = #{id}")
    public Book getById(Integer id);
}

application.yml 文件中配置:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db
    username: root
    password: root
    
mybatis:
  configuration:
    map-underscore-to-camel-case: true

test/java 的测试类中增加测试方法进行测试:

@SpringBootTest
class SpringbootMybaitsApplicationTests {

	@Autowired
	private BookDao bookDao;

	@Test
	void testGetById() {
		Book book = bookDao.getById(1);
		System.out.println(book);
	}
}

注意:

Spring Boot 版本低于 2.4.3,MySQL 驱动版本大于 8.0 时,需要在 url 连接串中配置时区 jdbc:mysql://localhost:3306/ssm_db?serverTimezone=Asia/Shanghai

整合 MinIO

MinIO 是一个高性能、开源的对象存储服务器,经常用于存储图片和文档。

官方网站open in new window下载对应平台的安装包,进行安装,步骤如下:

[root@stone ~]# mkdir /data/software
[root@stone ~]# mkdir /data/minio
[root@stone ~]# cd /data/software/
[root@stone ~]# wget https://dl.min.io/server/minio/release/linux-amd64/minio
[root@stone software]# chmod a+x minio
[root@stone software]# vi /root/.bash_profile
[root@stone software]# tail -2 /root/.bash_profile
export MINIO_ROOT_USER=stone
export MINIO_ROOT_PASSWORD=123456
[root@stone software]# source /root/.bash_profile
[root@stone software]# nohup ./minio server /data/minio/ --console-address ":9001" &

启动成功后,访问控制台open in new window,创建 Bucket。

pom.xml 文件中引入依赖:

        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.4.3</version>
        </dependency>

application.yml 文件中配置:

spring:
  servlet:
    multipart:
      max-file-size: 100MB

minio:
  endpoint: http://192.168.92.128:9000
  accessKey: stone
  secretKey: 123456
  bucketName: big-event
  url: http://192.168.92.128:9000/big-event

config 包下创建 MinioConfig 配置类:

@Data
@Configuration
public class MinioConfig {

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.accessKey}")
    private String accessKey;

    @Value("${minio.secretKey}")
    private String secretKey;

    @Value("${minio.bucketName}")
    private String bucketName;

    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }
}

utils 包下创建 MinioUtil 工具类:

@Component
public class MinioUtil {

    @Autowired
    private MinioConfig minioConfig;

    @Autowired
    private MinioClient minioClient;

    /**
     * 上传文件到 MinIO
     *
     * @param file       文件
     * @param objectName 存储对象名称
     * @return
     * 如果 Bucket 为 Public,则可以由 endpoint/bucket/filename 组成的 URL
     * 如果 Bucket 为 Private,则需要使用 getPresignedObjectUrl 获取不超过 7 天的 URL
     * 在实际开发中,Bucket 一般为 Private,此时不会将 URL 存储到数据库中,而是存储文件名到数据库中,通过文件名去下载文件
     */
    public void uploadFile(MultipartFile file, String objectName) throws Exception {
        PutObjectArgs args = PutObjectArgs.builder()
                .bucket(minioConfig.getBucketName())
                .object(objectName)
                .stream(file.getInputStream(), file.getSize(), -1)
                .contentType(file.getContentType())
                .build();
        minioClient.putObject(args);
    }

    /**
     * 从 MinIO 下载文件到 InputStream
     * @param objectName 文件对象名称
     * @return 文件流
     */
    public InputStream downloadFile(String objectName) throws Exception {
        GetObjectArgs args = GetObjectArgs.builder()
                .bucket(minioConfig.getBucketName())
                .object(objectName)
                .build();
        return minioClient.getObject(args);
    }
}

controller 包下创建 FileController 类:

@RestController
public class FileController {

    @Autowired
    private MinioUtil minioUtil;

    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file) throws Exception {
        String originalFilename = file.getOriginalFilename();
        String filename = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
        minioUtil.uploadFile(file, filename);
        return Result.success(filename);
    }

    @GetMapping("/download")
    public void download(@RequestParam String filename, HttpServletResponse response) throws Exception {
        InputStream inputStream = minioUtil.downloadFile(filename);
        response.setHeader("Content-Disposition", "attachment;filename=" + filename);
        response.setContentType("application/force-download");
        response.setCharacterEncoding("UTF-8");
        IOUtils.copy(inputStream, response.getOutputStream());
    }
}

然后就可以使用 Postman 测试文件的上传和下载。

整合 Redis

Redisopen in new window 是一款开源内存键值数据库,常用于缓存。

安装配置open in new window完成 Redis 后,在 pom.xml 文件中引入依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

application.yml 文件中配置:

spring:
  data:
    redis:
      host: 192.168.92.128
      port: 6379

创建 RedisTest 测试类进行测试:

@SpringBootTest // 如果测试类上添加了 @SpringBootTest 注解,那么将来单元测试方法执行之前,会自动初始化 Spring 容器,并自动注入所有被 Spring 管理的 Bean。
public class RedisTest {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void testSet() {
        stringRedisTemplate.opsForValue().set("username", "zhangsan");
        stringRedisTemplate.opsForValue().set("id", "1", 60, TimeUnit.SECONDS);
    }

    @Test
    public void testGet() {
        String username = stringRedisTemplate.opsForValue().get("username");
        System.out.println(username);
    }
}
上次编辑于:
贡献者: stonebox,stone