第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();
    }

}

执行结果如下:

相关推荐
Java学长-kirito2 小时前
springboot/ssm旅游民宿信息管理系统Java旅游景点管理系统web旅游源码
java·spring boot·旅游
B站计算机毕业设计超人2 小时前
计算机毕业设计Spark+SpringBoot旅游推荐系统 旅游景点推荐 旅游可视化 旅游爬虫 景区客流量预测 旅游大数据 大数据毕业设计
spring boot·爬虫·深度学习·机器学习·课程设计·数据可视化·推荐算法
ivwdcwso3 小时前
Django-Vue3-Admin - 现代化的前后端分离权限管理系统
后端·python·django
码农小旋风3 小时前
00-MySQL专栏大纲
后端
MYG_G3 小时前
安装Go语言(Golang)的详细教程
开发语言·后端·golang
knoci3 小时前
【Go】-调度器简介
开发语言·后端·学习·golang
西红柿计算机毕设4 小时前
基于大数据python 农业害虫识别系统(源码+LW+部署讲解+数据库+ppt)
大数据·开发语言·数据库·spring boot·python·深度学习·django
AskHarries4 小时前
如何在 Spring Cloud 集成 Micrometer Tracing?
后端
Hamm4 小时前
为了第三方快速对接,我们设计了这套基于Java的SDK方案
java·后端·架构