第2章 Spring Boot核心机制

为何以继承方式引入SpringBoot

提出疑问

以前我们在开发项目时,需要什么,引入对应的依赖就行,比如我们需要连接mysql数据,则引入mysql驱动的依赖,如下:

xml 复制代码
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <version>8.3.0</version>
</dependency>

现在我们要使用SpringBoot框架,按说也应该采用依赖的方式将SpringBoot框架引入,如下:

xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.3.5</version>
</dependency>

但是SpringBoot官方推荐的不是直接引入依赖,而是采用继承的方式实现,如下:

xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.5</version>
</parent>

为什么?

作为父项目和作为依赖的区别

继承父工程的优势

  • 依赖管理:可以在父工程中定义依赖的版本,子模块可以直接引用而不必指定版本号。
  • 插件管理:可以在父工程中配置常用的插件及其版本,子模块可以直接使用这些配置。
  • 属性设置:可以在父工程中定义一些通用的属性,如项目编码、Java 版本等。
  • 统一配置:可以统一多个子模块的构建配置,确保一致性。

直接引入依赖的局限性(如果你不使用继承父工程的方式,而是通过直接引入依赖的方式来管理项目,那么你将失去上述的一些优势)

  • 依赖版本管理:每个子模块都需要单独指定依赖的版本,这会导致大量的重复配置,并且难以维护。
  • 插件配置:每个子模块都需要单独配置插件及其版本,无法共享父工程中的插件配置。
  • 属性设置:每个子模块都需要单独设置通用的属性,如项目编码、Java 版本等。
  • 构建配置:每个子模块的构建配置需要单独维护,难以保证一致性。

总结:选择哪种方式取决于你的具体需求。

  • 如果你希望多个项目之间共享构建配置,那么使用父项目是一个好的选择;
  • 如果你只是想在项目之间共享代码,那么应该使用依赖关系。

原理揭晓

通过源码来分析一下:

通过上图源码可以看到Spring Boot预先对开发中需要用到的依赖进行了版本的统一管理。我们需要和SpringBoot框架共享这个构建配置。因此官方推荐使用继承的方式引入SpringBoot框架。

依赖统一管理的好处

Spring Boot 框架的一个重要特性就是简化了项目依赖管理。它通过提供一个叫做"依赖管理"的功能来帮助开发者更容易地管理和使用第三方库和其他 Spring 组件。具体来说,Spring Boot 提供了一个包含多个 Spring 和其他常用库的依赖版本配置文件(通常是在 spring-boot-dependencies 文件中),这使得开发者不需要在自己的项目中显式指定这些依赖的版本号。

这样做有以下几个好处:

  1. 简化依赖声明
    开发者只需要在 pom.xml 文件中声明需要的依赖而不需要指定其版本号,因为 Spring Boot 已经为这些依赖指定了版本。例如,如果你需要使用mysql驱动,你只需要添加相应的依赖声明而不需要关心版本。
xml 复制代码
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
  1. 避免版本冲突
    当多个库之间存在依赖关系的时候,如果手动管理版本可能会导致版本之间的冲突(即"依赖地狱")。Spring Boot 提供的统一版本管理可以减少这种冲突的可能性。
  2. 易于升级
    当 Spring Boot 发布新版本时,通常会更新其依赖库到最新稳定版。因此,当你升级 Spring Boot 版本时,它所管理的所有依赖也会随之更新到兼容的版本。
  3. 减少配置错误
    由于 Spring Boot 自动处理了依赖的版本,减少了手动输入版本号可能引入的拼写或格式错误。
  4. 提高开发效率
    开发者可以专注于业务逻辑的编写,而不是花费时间在解决依赖问题上。

总的来说,Spring Boot 的依赖管理功能使得开发者可以更加专注于业务逻辑的实现,同时减少了因依赖版本不一致而引发的问题,提高了项目的可维护性和开发效率。

当然,如果你在项目中需要更改某个依赖的版本号,不想使用SpringBoot框架指定的版本号,只需要在引入依赖时强行执行版本号即可,maven是支持就近原则的:

这样做就是采用SpringBoot指定版本的依赖:

xml 复制代码
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

这样做就是不采用SpringBoot指定版本的依赖:

xml 复制代码
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.2.0</version>
</dependency>

Starter-启动器

在 Spring Boot 中,启动器(Starter)本质上是一个简化依赖管理的概念。Spring Boot 的启动器本质上就是一组预定义的依赖集合,它们被组织成一个个 Maven的依赖,以方便开发者快速集成特定的功能模块。如果你想做web开发,只需要引入web启动器。web启动器会自动引入web开发所需要的子依赖。

启动器实现原理

  1. 依赖聚合
    每个启动器通常对应一个特定的功能集或者一个完整的应用模块,如 spring-boot-starter-web 就包含了构建 Web 应用所需的所有基本依赖项,如 Spring MVC, Tomcat 嵌入式容器等。
  2. 依赖传递
    当你在项目中引入一个启动器时,它不仅会把自身作为依赖加入到你的项目中,还会把它的所有直接依赖项(transitive dependencies)也加入进来。这意味着你不需要单独声明这些依赖项,它们会自动成为项目的一部分。
  3. 版本管理
    启动器内部已经指定了所有依赖项的具体版本,这些版本信息存储在一个公共的 BOM(Bill of Materials,物料清单)文件中,通常是 spring-boot-dependencies。当引入启动器时,实际上也间接引用了这个 BOM,从而确保了所有依赖项版本的一致性。
  4. 自动配置
    许多启动器还提供了自动配置(Auto-configuration),这是一种机制,允许 Spring Boot 根据类路径上的可用组件自动设置你的应用程序。例如,如果类路径上有 Spring MVC 和嵌入式 Tomcat,则 Spring Boot 会自动配置它们,并准备好一个 web 应用程序。

使用启动器的示例

假设你想创建一个基于 Spring MVC 的 RESTful Web 应用,你可以简单地将 spring-boot-starter-web 添加到你的项目中:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

当你添加这个依赖时,Spring Boot 会处理所有必要的细节,包括添加 Spring MVC 和 Tomcat 作为嵌入式 Servlet 容器,并且根据类路径上的内容进行适当的自动配置。如下图所示:

这就是 Spring Boot 启动器的基本实现原理,它简化了依赖管理,让开发者能够更专注于业务逻辑的实现。

都有哪些启动器

启动器通常包括:

  • SpringBoot官方提供的启动器
  • 非官方提供的启动器

官方提供的启动器

启动器命名特点:spring-boot-starter-*

非官方的启动器

启动器命名特点:*-spring-boot-starter

Spring Boot核心注解

创建一个新的模块,来学习Spring Boot核心注解:

只加入web启动器

@SpringBootApplication注解

Spring Boot的主入口程序被@SpringBootApplication注解标注,可见这个注解的重要性,查看它的源码:

可以看出这个注解属于组合注解。拥有@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan的功能。

@SpringBootConfiguration注解

  • @SpringBootConfiguration注解的源码如下:

可以看到这个注解的被@Configuration标注,说明主入口程序是一个配置类。也就是说主入口中的方法可以被@Bean注解标注,被@Bean注解的标注的方法会被Spring容器自动调用,并且将该方法的返回对象纳入IoC容器的管理。测试一下:

java 复制代码
@SpringBootApplication
public class Sb305CoreApplication {

    @Bean
    public Date getNowDate(){ // 方法名作为bean的id
        return new Date();
    }

    /**
     * 主程序入口
     * 本方法负责启动Spring Boot应用,并从Spring应用上下文中获取Bean进行展示
     * @param args 命令行参数,允许从命令行传递参数到程序
     */
    public static void main(String[] args) {

        // 启动Spring Boot应用,返回应用上下文
        ConfigurableApplicationContext applicationContext = SpringApplication.run(Sb305CoreApplication.class, args);

        // 从应用上下文中获取Date类型的Bean
        Date dateBean1 = applicationContext.getBean(Date.class);
        System.out.println(dateBean1);

        // 从应用上下文中根据Bean名称获取特定的Date类型Bean
        Date dateBean2 = applicationContext.getBean("getNowDate", Date.class);
        System.out.println(dateBean2);
    }
}

执行结果:

通过测试我们也认证了这一点:SpringBoot主入口类实际上就是一个配置类。这个配置类也可以称为,起源的意思,SpringBoot从这个配置类开始加载项目中所有的bean。

@EnableAutoConfiguration注解

该注解表示启用自动配置。Spring Boot 会根据你引入的依赖自动为你配置好一系列的 Bean,无需手动编写复杂的配置代码。

例如:如果你在SpringBoot项目中进行了如下配置:

properties 复制代码
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=123456

并且在依赖中引入了mybatis依赖/mybatis启动器,那么SpringBoot框架将为你自动化配置以下bean:

  • SqlSessionFactory: MyBatis的核心工厂SqlSessionFactory会被自动配置。这个工厂负责创建SqlSession实例,后者用来执行映射文件中的SQL语句。
  • TransactionManager: DataSourceTransactionManager会被自动配置来管理与数据源相关的事务。

@ComponentScan注解

这个注解的作用是:启动组件扫描功能,代替spring框架xml文件中这个配置:

xml 复制代码
<context:component-scan base-package="com.liming.sb305core"/>

因此被@SpringBootApplication注解标注之后,会启动组件扫描功能,扫描的包是主入口程序所在包及子包,因此如果一个bean要纳入IoC容器的管理则必须放到主入口程序所在包及子包下。放到主入口程序所在包之外的话,扫描不到。

外部化配置

什么是外部化配置

外部化配置是指:将配置信息存储在应用程序代码之外的地方。这样配置信息可以独立于代码进行管理。这样方便了配置的修改,并且修改后不需要重新编译代码,也不需要重新部署项目。

外部化配置的方式

SpringBoot支持多种外部化配置方式,包括但不限于:

  • properties文件
  • YAML文件
  • 系统环境变量
  • 命令行参数
  • ...

外部化配置的优势

  1. 灵活性:配置文件可以独立于应用程序部署,这使得可以根据运行环境的不同来调整配置,而无需修改代码。
  2. 易于维护:配置变更不需要重新构建和部署应用程序,降低了维护成本。
  3. 安全性:敏感信息如数据库密码、API密钥等可以存储在外部,并且可以限制谁有权限访问这些配置信息。
  4. 共享性:多实例或多服务可以共享相同的配置信息,减少重复配置的工作量。
  5. 版本控制:配置文件可以存放在版本控制系统中,便于跟踪历史版本和回滚配置。

外部化配置对比传统配置

在传统的SSM三大框架中,如果修改XML的配置后,需要对应用重新打包,重新部署。使用SpringBoot框架的外部化配置后,修改配置后,不需要对应用重新打包,也不需要重新部署,最多重启一下服务即可。

application.properties

  • application.properties配置文件是SpringBoot框架默认的配置文件。

  • application.properties不是必须的,SpringBoot对于应用程序来说,都提供了一套默认配置(就是我们所说的自动配置)。

  • 如果你要改变这些默认的行为,可以在application.properties文件中进行配置。

  • application.properties可以放在类路径当中,也可以放在项目之外。因此称为外部化配置。

Spring Boot 框架在启动时会尝试从以下位置加载 application.properties 配置文件:

  1. file:./config/ :首先在Spring Boot 当前工作目录下的 config 文件夹中查找。
    1. 注意:如果没有找到application.properties会继续找application.yml,如果这两个都没有找到,才会进入以下位置查找,以此类推。
  2. file:./ :如果在当前工作目录下config目录中找不到时,再从当前工作目录中查找。
  3. classpath:/config/ :如果从工作目录中找不到,会从类路径中找,先从类路径的 /config/ 目录下寻找配置文件。
  4. classpath:/ :如果在 /config/ 下没有找到,它会在类路径的根目录下查找。

Spring Boot 会按照这个顺序来加载配置文件,如果在多个位置有相同的属性定义,那么最先检查的位置中的属性值将优先使用。

如果你想要指定其他的配置文件位置或者改变默认的行为,可以通过 --spring.config.location= 后跟路径的方式来指定配置文件的具体位置。例如 :

plain 复制代码
java -jar sb3-01-first-web-1.0-SNAPSHOT.jar --spring.config.location=file:///E:\a\b\application.properties

这样,Spring Boot 将会首先从 E:\a\b\ 这个路径加载配置文件。注意,这种方式可以用来覆盖默认的配置文件位置,并且可以结合以上提到的位置一起使用。

注意:以上的--spring.config.location=file:///E:\a\b\application.properties就属于命令行参数,它将来会被传递到main方法的(String[] args)参数上。

使用@Value注解

@Value注解可以将application.properties/application.yml文件中的配置信息注入/绑定到java对象的属性上。

语法格式:@Value("${key}")

使用@Value注解时也可以指定默认值,当指定默认值时,如果配置文件中没有指定配置值,则采用默认值。

语法格式:@Value("${key:defalut}")

YAML

YAML概述

  • SpringBoot采用集中式配置管理,所有的配置都编写到一个配置文件中:application.properties。如果配置非常多,层级不够分明,因此SpringBoot为了提高配置文件可读性,也支持YAML格式的配置文件:application.yml
  • YAML(YAML Ain't Markup Language)是一种人类可读的数据序列化格式,它通常用于配置文件,在各种编程语言中作为一种存储或传输数据的方式。YAML的设计目标是易于阅读和编写,同时保持足够的表达能力来表示复杂的数据结构。
  • YAML文件的扩展名可以是.yaml.yml

YAML的语法规则

YAML的语法规则如下:

  1. 数据结构:YAML支持多种数据类型,包括:

    1. 字符串、数字、布尔值
    2. 数组、list集合
    3. map键值对 等。
  2. YAML使用一个空格来分隔属性名属性值,例如:

    1. properties文件中这样的配置:name=jack
    2. yaml文件中需要这样配置:name: jack
  3. YAML用换行+空格来表示层级关系。注意不能使用tab,必须是空格,空格数量无要求,大部分建议2个或4个空格。例如:

    1. properties文件中这样的配置:myapp.name=mall
    2. yaml文件中就需要这样配置:
    yaml 复制代码
    myapp:
      name: mall
  4. 同级元素左对齐。例如:

    1. properties文件中有这样的配置:
    properties 复制代码
    myapp.name=mall
    myapp.count=10
    1. yaml文件中就应该这样配置:
    yaml 复制代码
    myapp:
      name: mall
      count: 10
  5. 键必须是唯一的:在一个映射中,键必须是唯一的。

  6. 注释:使用#进行注释。

  7. 大小写敏感

YAML的使用小细节

第一:普通文本也可以使用单引号或双引号括起来:(当然普通文本也可以不使用单引号和双引号括起来。)

  • 单引号括起来:单引号内所有的内容都被当做普通文本,不转义(例如字符串中有\n,则\n被当做普通的字符串)
  • 双引号括起来:双引号中有 \n 则会被转义为换行符

第二:保留文本格式

  • | 将文本写到这个符号的下层,会自动保留格式。

第三:文档切割

  • --- 这个符号下面的配置可以认为是一个独立的yaml文件。便于庞大文件的阅读。

application.yml

Spring Boot框架同时支持propertiesyaml

强调:在同一个目录下同时存在application.propertiesapplication.yml时,SpringBoot优先解析application.properties文件。

resources/config目录下新建application.yml文件,进行如下配置:

yaml 复制代码
myapp:
  username: jim
  email: jim@123.com
  age: 40
  password: jim123

一定要把resources/config目录下application.properties名字修改为application2.properties,这样Spring Boot才会解析resources/config/application.yml

配置文件合并

一个项目中所有的配置全部编写到application.properties文件中,会导致配置臃肿,不易维护,有时我们会将配置编写到不同的文件中,例如:application-mysql.properties专门配置mysql的信息,application-redis.properties专门配置redis的信息,最终将两个配置文件合并到一个配置文件中。

properties文件

application-mysql.properties

properties 复制代码
spring.datasource.username=root
spring.datasource.password=123456

application-redis.properties

properties 复制代码
spring.data.redis.host=localhost
spring.data.redis.port=6379

application.properties

properties 复制代码
spring.config.import=classpath:application-mysql.properties,classpath:application-redis.properties

编写service测试,看看能否拿到配置信息:

java 复制代码
package com.liming.sb307externalconfig.service;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service("userServiceMulti")
public class UserServiceMulti {
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;
    @Value("${spring.data.redis.host}")
    private String host;
    @Value("${spring.data.redis.port}")
    private String port;
    
    public void printInfo(){
        String str = String.join(",", username, password, host, port);
        System.out.println(str);
    }
}

yaml文件

application-mysql.yml

yaml 复制代码
spring:
  datasource:
    username: root
    password: 789789

application-redis.yml

yaml 复制代码
spring:
  data:
    redis:
      host: localhost
      port: 6379

application.yml

yaml 复制代码
spring:
  config:
    import:
      - classpath:application-mysql.yml
      - classpath:application-redis.yml

多环境切换

在Spring Boot中,多环境切换是指在一个应用程序中支持多种运行环境配置的能力。这通常用于区分开发(development)、测试(testing)、预生产(staging)和生产(production)等不同阶段的环境。这种功能使得开发者能够在不同的环境中使用不同的配置,比如数据库连接信息、服务器端口、环境变量等,而不需要更改代码。这对于维护一个可移植且易于管理的应用程序非常重要。

  1. 开发环境的配置文件名一般叫做:application-dev.properties
properties 复制代码
spring.datasource.username=dev
spring.datasource.password=dev123
spring.datasource.url=jdbc:mysql://localhost:3306/dev
  1. 测试环境的配置文件名一般叫做:application-test.properties
properties 复制代码
spring.datasource.username=test
spring.datasource.password=test123
spring.datasource.url=jdbc:mysql://localhost:3306/test
  1. 预生产环境的配置文件名一般叫做:application-preprod.properties
properties 复制代码
spring.datasource.username=preprod
spring.datasource.password=preprod123
spring.datasource.url=jdbc:mysql://localhost:3306/preprod
  1. 生产环境的配置文件名一般叫做:application-prod.properties
properties 复制代码
spring.datasource.username=prod
spring.datasource.password=prod123
spring.datasource.url=jdbc:mysql://localhost:3306/prod

如果你希望该项目使用生产环境的配置,你可以这样做:

  • 第一种方式:在application.properties文件中添加这个配置:spring.profiles.active=prod
  • 第二种方式:在命令行参数上添加:--spring.profiles.active=prod

将配置绑定到bean

绑定简单bean

SpringBoot配置文件中的信息除了可以使用@Value注解读取之外,也可以将配置信息一次性赋值给Bean对象的属性。例如有这样的配置:

application.yml

yaml 复制代码
app:
  name: jack
  age: 30
  email: jack@123.com

Bean需要这样定义:

java 复制代码
package com.liming.sb307externalconfig.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "app")
public class AppBean {
    private String name;
    private Integer age;
    private String email;

    @Override
    public String toString() {
        return "AppBean{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

说明:

  1. 被绑定的bean,需要使用@ConfigurationProperties(prefix = "app")注解进行标注,prefix用来指定前缀,哪个是前缀,如下图所示:

配置文件中的nameageemail要和bean对象的属性名nameageemail对应上。(属性名相同)

并且bean中的所有属性都提供了setter方法。因为底层是通过setter方法给bean属性赋值的。

  1. 这样的bean需要使用@Component注解进行标注,纳入IoC容器的管理。@Component注解负责创建Bean对象,@ConfigurationProperties(prefix = "app")注解负责给bean对象的属性赋值。
  2. bean的属性需要是非static的属性。

@Configuration注解

以上操作中使用了@Component注解进行了标注,来纳入IoC容器的管理。也可以使用另外一个注解@Configuration,用这个注解将Bean标注为配置类。多数情况下我们会选择使用这个注解,因为该Bean对象的属性对应的就是配置文件中的配置信息,因此这个Bean我们也可以将其看做是一个配置类。

java 复制代码
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppBean {
    private String name;
    private Integer age;
    private String email;
    //setter and getter
}

绑定嵌套bean

当一个Bean中嵌套了一个Bean,这种情况下可以将配置信息绑定到该Bean上吗?当然可以。

有这样的一个配置:

yaml 复制代码
app:
  name: jack
  age: 30
  email: jack@123.com
  address: 
    city: BJ
    street: ChaoYang
    zipcode: 123456

需要编写这样的两个Bean:

java 复制代码
package com.liming.sb307externalconfig.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false) //proxyBeanMethods = false取消代理机制
@ConfigurationProperties(prefix = "app")
public class AppBean {
    private String name;
    private Integer age;
    private String email;
    private Address address;

    @Override
    public String toString() {
        return "AppBean{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", address=" + address +
                '}';
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
java 复制代码
package com.liming.sb307externalconfig.bean;

public class Address {
    private String city;
    private String street;
    private String zipcode;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                ", street='" + street + '\'' +
                ", zipcode='" + zipcode + '\'' +
                '}';
    }
}

执行测试程序,结果如下:


@EnableConfigurationProperties与@ConfigurationPropertiesScan

AppBean纳入IoC容器的管理,之前我们说了两种方式:第一种是使用@Component,第二种是使用@Configuration。SpringBoot其实还提供了另外两种方式:

  • 第一种:@EnableConfigurationProperties
  • 第二种:@ConfigurationPropertiesScan

这两个注解都是标注在SpringBoot主入口程序上的:

java 复制代码
@EnableConfigurationProperties(AppBean.class)
@SpringBootApplication
public class Sb307ExternalConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(Sb307ExternalConfigApplication.class, args);
    }
}

或者

java 复制代码
@ConfigurationPropertiesScan(basePackages = "com.liming.sb307externalconfig.bean")
@SpringBootApplication
public class Sb307ExternalConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(Sb307ExternalConfigApplication.class, args);
    }
}

将配置赋值到Bean的Map/List/Array属性上

代码如下:

java 复制代码
package com.liming.sb307externalconfig.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@ConfigurationProperties
public class CollectionConfig {
    private String[] names;
    private List<Product> products;
    private Map<String, Vip> vips;

    @Override
    public String toString() {
        return "CollectionConfig{" +
                "names=" + Arrays.toString(names) +
                ", products=" + products +
                ", vips=" + vips +
                '}';
    }

    public String[] getNames() {
        return names;
    }

    public void setNames(String[] names) {
        this.names = names;
    }

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }

    public Map<String, Vip> getVips() {
        return vips;
    }

    public void setVips(Map<String, Vip> vips) {
        this.vips = vips;
    }
}

class Product {
    private String name;
    private Double price;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }
}

class Vip {
    private String name;
    private Integer age;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

配置信息如下:application.yml

yaml 复制代码
#数组
names:
  - jackson
  - lucy
  - lili

#List集合
products: 
  - name: 西瓜
    price: 3.0
  - name: 苹果
    price: 2.0

#Map集合
vips:
  vip1:
    name: 张三
    age: 20
  vip2:
    name: 李四
    age: 22

提醒:记得入口程序使用@ConfigurationPropertiesScan(basePackages = "com.liming.sb307externalconfig.bean")进行标注。

将配置绑定到第三方对象

将配置文件中的信息绑定到某个Bean对象上,如果这个Bean对象没有源码,是第三方库提供的,怎么办?此时可以单独编写一个方法,在方法上使用以下两个注解进行标注:

  • @Bean
  • @ConfigurationProperties

假设我们有这样一个类Address,代码如下:

java 复制代码
package com.liming.sb307externalconfig.bean;

public class Address {
    private String city;
    private String street;
    private String zipcode;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                ", street='" + street + '\'' +
                ", zipcode='" + zipcode + '\'' +
                '}';
    }
}

当然,我们是看不到这个源码的,只知道有这样一个字节码Address.class。大家也可以看到这个Address类上没有添加任何注解。假设我们要将以下配置绑定到这个Bean上应该怎么做?

yaml 复制代码
address:
  city: TJ
  street: XiangYangLu
  zipcode: 11111111

实现代码如下:

java 复制代码
@Configuration
public class ApplicationConfig {
    @Bean
    @ConfigurationProperties(prefix = "address")
    public Address getAddress(){
        return new Address();
    }
}

指定数据来源

之前所讲的内容是将Spring Boot框架默认的配置文件application.propertiesapplication.yml作为数据的来源绑定到Bean上。如果配置信息没有在默认的配置文件中呢?可以使用@PropertySource注解指定配置文件的位置,这个配置文件可以是.properties,也可以是.xml

resources目录下新建a目录,在a目录下新建b目录,b目录中新建group-info.properties文件,进行如下的配置:

properties 复制代码
group.name=IT
group.leader=LaoDu
group.count=20

定义Java类Group,然后进行注解标注:

java 复制代码
package com.liming.sb307externalconfig.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@ConfigurationProperties(prefix = "group")
@PropertySource("classpath:a/b/group-info.properties")
public class Group {
    private String name;
    private String leader;
    private Integer count;

    @Override
    public String toString() {
        return "Group{" +
                "name='" + name + '\'' +
                ", leader='" + leader + '\'' +
                ", count=" + count +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLeader() {
        return leader;
    }

    public void setLeader(String leader) {
        this.leader = leader;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }
}

以下三个注解分别起到什么作用:

  • @Configuration:指定该类为配置类,纳入Spring容器的管理
  • @ConfigurationProperties(prefix = "group"):将配置文件中的值赋值给Bean对象的属性
  • @PropertySource("classpath:a/b/group-info.properties"):指定额外的配置文件

Spring Boot中如何进行AOP的开发

Spring Boot AOP概述

Spring Boot的AOP编程和Spring框架中AOP编程的唯一区别是:引入依赖的方式不同。其他内容完全一样。Spring Boot中AOP编程需要引入aop启动器:

xml 复制代码
<!--aop启动器-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

可以看到,当引入aop启动器之后,会引入aop依赖aspectj依赖

  • aop依赖:如果只有这一个依赖,也可以实现AOP编程,这种方式表示使用了纯Spring AOP实现aop编程。
  • aspectj依赖:一个独立的可以完成AOP编程的AOP框架,属于第三方的,不属于Spring框架。(我们通常用它,因为它的功能更加强大)

Spring Boot AOP实现

实现功能:项目中很多service,要求执行任何service中的任何方法之前记录日志。

创建Spring Boot项目引入aop启动器

项目名:sb3-08-aop

xml 复制代码
<!--aop启动器-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

编写service并提供方法

java 复制代码
package com.liming.service;

public interface OrderService {

    /**
     * 生成订单
     */
    void generate();

    /**
     * 订单详情
     */
    void detail();
}
java 复制代码
package com.liming.service.impl;

import com.liming.service.OrderService;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl implements OrderService {
    /**
     * 生成订单
     */
    @Override
    public void generate() {
        System.out.println("生成订单");
    }

    /**
     * 订单详情
     */
    @Override
    public void detail() {
        System.out.println("订单详情");
    }
}

编写切面

java 复制代码
package com.liming.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component // 纳入IoC容器
@Aspect // 指定该类为切面类
public class LogAspect {
    // 日期格式化器
    private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS");

    // 前置通知
    // 切入点表达式:service包下任意类的任意方法
    @Before("execution(* com.liming.service..*.*(..))")
    public void sysLog(JoinPoint joinPoint) throws Throwable {
        StringBuilder log = new StringBuilder();
        LocalDateTime now = LocalDateTime.now();
        String strNow = formatter.format(now);
        // 追加日期
        log.append(strNow);
        // 追加冒号
        log.append(":");
        // 追加方法签名
        log.append(joinPoint.getSignature().getName());
        // 追加方法参数
        log.append("(");
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            log.append(args[i]);
            if(i < args.length - 1) {
                log.append(",");
            }
        }
        log.append(")");
        System.out.println(log);
    }
}

测试

java 复制代码
package com.liming;

import com.liming.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Sb308AopApplicationTests {

    @Autowired
    private OrderService orderService;

    @Test
    void contextLoads() {
        orderService.generate();
        orderService.detail();
    }

}

执行结果如下:

相关推荐
逸风尊者4 分钟前
开发可掌握的知识:推荐系统
java·后端·算法
Violet_YSWY8 分钟前
阿里巴巴状态码
后端
灵魂猎手13 分钟前
Antrl4 入门 —— 使用Antrl4实现一个表达式计算器
java·后端
moxiaoran575323 分钟前
Go语言的递归函数
开发语言·后端·golang
IT 行者1 小时前
Spring Security 7.0 新特性详解
java·后端·spring
华仔啊1 小时前
Java 的金额计算用 long 还是 BigDecimal?资深程序员这样选
java·后端
12344521 小时前
【MCP入门篇】从0到1教你搭建MCP服务
后端·mcp
okseekw1 小时前
Java多线程开发实战:解锁线程安全与性能优化的关键技术
java·后端
HuangYongbiao1 小时前
NestJS 架构设计系列:应用服务与领域服务的区别
后端·架构