SpringBoot 配置文件

目录

[1. 配置文件](#1. 配置文件)

[1.1 什么是配置文件](#1.1 什么是配置文件)

[1.2 配置文件存放的内容](#1.2 配置文件存放的内容)

[1.3 配置文件的作用](#1.3 配置文件的作用)

[2. SpringBoot 配置文件](#2. SpringBoot 配置文件)

[3. properties](#3. properties)

[3.1 读取配置文件](#3.1 读取配置文件)

[3.1.1 @Value](#3.1.1 @Value)

[3.1.2 @PostConstruct](#3.1.2 @PostConstruct)

[3.2 properties 缺点](#3.2 properties 缺点)

[4. yml](#4. yml)

[4.1 yml 基本语法](#4.1 yml 基本语法)

[4.2 yml 配置数据类型](#4.2 yml 配置数据类型)

[4.2.1 yml 读取配置](#4.2.1 yml 读取配置)

[4.3 yml 配置对象](#4.3 yml 配置对象)

[4.3.1 读取对象数据](#4.3.1 读取对象数据)

[4.2 yml 配置集合](#4.2 yml 配置集合)

[4.2.1 配置 List & Map](#4.2.1 配置 List & Map)

[5. 综合练习](#5. 综合练习)

[5.1 验证码案例](#5.1 验证码案例)

[5.1.1 需求](#5.1.1 需求)

[5.1.2 接口文档](#5.1.2 接口文档)

[5.1.2.1 生成验证码](#5.1.2.1 生成验证码)

[5.1.2.2 校验用户输入](#5.1.2.2 校验用户输入)

[5.1.3 接口一: 生成验证码](#5.1.3 接口一: 生成验证码)

[5.1.4 接口二: 校验用户输入](#5.1.4 接口二: 校验用户输入)

[5.1.5 后端代码](#5.1.5 后端代码)

[5.1.6 前端代码](#5.1.6 前端代码)


1. 配置文件

1.1 什么是配置文件

其实, 我们一直都在接触配置文件, 例如一个项目中的 .idea 文件夹中的文件, 就是一个配置文件:

这些配置文件中包含了 IDEA 的背景色, 字体大小, Maven 仓库的地址, ....... 每个项目都有自己的配置文件.

包括我们电脑中的 user 文件夹, 里面也有配置文件:

1.2 配置文件存放的内容

上文说到, 每个项目都有不同的设置, 因此具有不同的配置文件.

那么, 对于用户来说也是一样的, 为了满足每个用户可能存在的不同需求, 我们将可能发生变化的信息, 从代码中分离出来, 放置在配置文件中, 实现和代码的解耦, 以便在不修改代码的情况下, 根据用户的偏好或需求进行调整, 从而解决硬编码问题.

硬编码, 就是指代码写死的, 难以修改的.

1.3 配置文件的作用

因此, 配置文件的作用如下:

  • 通过修改配置文件, 可以根据用户需求对程序相关功能进行更改, 而无需修改代码和重新部署, 因此解决了硬编码问题.

2. SpringBoot 配置文件

SpringBoot 配置文件有以下三种(文件名必须为 application, 不可修改):

  1. application.properties
  2. application.yml
  3. application.yaml

properties、yml 和 yaml 不仅仅代表 SpringBoot 的配置文件, 更重要的是它们代表了不同的**文件格式,**每种格式都有自己的语法规则.

其中, yml 就是 yaml 的简写(就像 htm 是 html 的简写一样), 两者代表的是同一个东西.

3. properties

properties 的语法为 key-value 格式, key 和 value 之间使用 = 进行分割. 如:"key = value"

建议: 所有单词小写, 单词之间使用 点(.) 进行分割.

Spring 项目创建时, 就已经帮我们创建了 application.properties 格式的配置文件:

我们可以在配置文件中修改服务器的端口号:

3.1 读取配置文件

3.1.1 @Value

除了程序自带的配置信息外, 我们也可以添加自定义的配置信息:

我们可以通过 @Value 注解将配置信息赋值给属性, 进而读取这些配置信息:

Spring 通过 @Value 拿到配置信息后(初始为字符串类型), 会根据属性的类型, 对字符串进行类型转换.

如果可以转换, 则成功:

如果不可以转换, 则报错:

3.1.2 @PostConstruct

除了上述通过 http 请求来展示配置信息的方式外, 我们还可以通过 @PostConstruct 注解来展示获取到的信息.

@PostConstruct 是一个方法注解.

  1. 当前类的 Bean 完成实例化 (注意是实例化, 不是初始化)

  2. Bean 中所依赖的其他 Bean(属性 Bean) 完成完成注入(初始化)

  3. 执行 @PostConstruct

实例化: 申请内存空间, 赋予默认值(null / 0 / false 等)

初始化: 把当前类的 Bean 所依赖的其他 Bean(属性 Bean) 完成注入

因此, 在该方法执行时, 当前类 Bean 中的属性 Bean 已经被注入(完成了初始化), 因此不会出现空指针异常的情况.

3.2 properties 缺点

由于 properties 的语法, 使得 properties 类型的配置文件有一个明显的缺点: 配置信息冗余.

例如, 数据库相关的配置文件:

此时, 就需要 yml 出场了.

4. yml

  1. application.yml 可以和 application.properties 并存在 SpringBoot 项目中, 并且两个配置文件同时生效(取并集).
  2. 若两个配置文件存在冲突的部分(如: 在两个文件中都修改了端口号), 那么会以 .properties 为准(properties 的优先级更高).

4.1 yml 基本语法

yml 采用树形结构, 基本语法如下图所示:

接下来展示一下 properties 和 yml 的对比:

尤其需要注意的是:

  1. 第一级不能有空格
  2. 第一级的 key 名不能重复出现
  3. 同一级一定要对齐

yml 中其实也可以使用 点(.) 来进行单词间的分割, 就像 properties 形式一样, 但是 key 和 value 一定要使用 冒号(:) 来分割:

4.2 yml 配置数据类型

复制代码
# 字符串
string:
  value: Hello
# 布尔值,true或false
boolean: 
  value: true
  value1: false
# 整数
int:
  value: 10
# 浮点数
float: 
  value: 3.14159
# Null,~代表null
# null 是特殊字符, 需要使用 "" 引起来
"null": 
  value: ~
# 空字符串(以下几种均表示 空字符串)
empty: 
  value: ""
  value1: ''
  value2: 

需要额外注意的是 null 和 空字符串.

4.2.1 yml 读取配置

读取 yml 配置文件中的信息, 方式和 properties 是一模一样的.

同样是使用 @Value 将配置信息注入到属性中, 并且单词间使用 点(.) 进行分割:

4.3 yml 配置对象

yml 定义对象, 和我们上面定义数据时的语法格式是相同的:

上图表示的就是, 一个 person 类中, 定义了 id, name, age 三个属性.

4.3.1 读取对象数据

@ConfigurationProperties 是一个类注解和方法注解.

对类使用, 并指定其 prefix 属性, 表示将配置中前缀为 prefix 的信息, 读取到该 Java 类的属性中.

注意:

使用 @ConfigurationProperties 将 JavaBean 中的属性和 配置文件中对象的属性进行匹配时, 两者名字要以一一对应!!

Bean 的生命周期如下:

同样, 也可以使用 @Value 读取配置中对象的某一属性信息:

因此, @Value 和 @ConfigurationProperties 的关系如下:

  1. 本质都是将配置数据读取到属性的 Bean 中,用于实现配置的注入,且都发生在 Bean 初始化过程中
  2. @Value: 用于注入单个属性的值. 通常用于注入简单类型的属性, 例如字符串、数字、布尔值等.
  3. @ConfigurationProperties: 用于将一个配置对象的信息, 注入到整个 Java 类的属性中.

4.2 yml 配置集合

4.2.1 配置 List & Map

在配置文件中配置 List(或数组), Map, 也是以对象的形式来配置的:

读取 List 和 Map 的配置信息, 同样是使用 @ConfigurationProperties:


5. 综合练习

5.1 验证码案例

5.1.1 需求

  1. 页面生成验证码
  2. 输入验证码, 点击提交, 验证用户输入验证码是否正确, 正确则进行页面跳转

5.1.2 接口文档

后端需要提供两个接口:

  1. 生成验证码图片
  2. 校验用户输入的验证码是否正确
5.1.2.1 生成验证码

请求: (前端给后端服务器发送一个请求)

  • URL: /captcha/getCaptcha

响应: 后端生成一个验证码的图片, 显示在页面上

5.1.2.2 校验用户输入

请求 URL: /captcha/check

请求参数: 用户输入的值

响应: 根据用户输入的验证码, 校验验证码是否正确. true: 验证成功. false: 验证失败

5.1.3 接口一: 生成验证码

我们借助开源的 hutool 来生成验证码: Hutool参考文档

当然, 要想使用 hutool, 第一步是先引入 hutool 的 Maven 依赖:

引入 hutool 依赖后, 我们就能够导入 hutool 提供的包, 使用 hutool 所提供的接口了:

了解了 hutool 后, 接下来正式开始接口的定义.

因为我们后端生成的验证码大小, 是可变的, 因此可以将验证码图片的高, 宽, ... 定义在配置文件中:


(注意: 内部类 Session 必须是静态的!!)

并且, 生成验证码后, 我们需要存储两个值:

  1. 将验证码的正确值, 存储在用户的 Session 中, 以便后续在验证接口中获取并验证用户输入的值是否正确
  2. 将用户开始填写时(生成验证码完毕时)的时间戳存储到用户 session 中, 以便后续在验证接口中获取并验证用户输入所用时间是否超时

演示:

在这个接口中, 我们需要注意两点:

1. 手动将响应的 Content-Type 设置为 "image/jpeg"

首先, Spring 确实会根据返回的内容自动推测 Content-Type. 如果你返回的是一张图片, Spring 会根据图片的格式(例如 .jpg, .png, .gif 等)自动设置合适的 Content-Type.此外, 浏览器也会有一些智能机制, 自动通过内容来推测 Content-Type.

但是, 为了避免不必要的兼容性问题或误判, 最好还是手动设置 Content-Type, 明确告诉浏览器类型, 确保浏览器按照该类型进行解析.

java 复制代码
// 手动将响应的 Content-Type 设置为 图片格式, 以便浏览器进行解析
response.setContentType("image/jpeg");

2. 将 get 请求设置为无缓存形式

当我们通过浏览器的地址栏输入 URL 访问一个页面时, 此时的请求方式是 GET 请求.

而浏览器和服务器通常会缓存 GET 请求的响应, 目的是提高页面加载速度并减少不必要的网络请求.

因此, 当用户连续刷新页面时, 返回的都可能是同一个验证码.

取消 get 请求缓存的方式有两种:

  1. 通过前端取消缓存(通常)
  2. 通过后端取消缓存

在实际工作中, 通常由前端解决这个问题.

前端解决方式:

html 复制代码
  <!-- 将 get 请求设置为无缓存形式(放在 head 标签中) -->
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
  <meta http-equiv="Pragma" content="no-cache" />
  <meta http-equiv="Expires" content="0" />

后端的解决方式:

java 复制代码
// 设置无缓存形式(通常是由前端处理的)
response.setHeader("Pragma", "no-Cache");

3. 将 Spring 所管理类的内部类设为静态

我们上文说到, Session 必须作为一个静态内部类存在, 接下来解释原因.

如果内部类不是静态的, 那么外部类 Bean 和内部类 Bean 就会形成一个 "死循环", 导致 Spring 无法管理. 原因:

  1. 要创建外部类的 Bean, Spring 需要先实例化外部类
  2. 但是,要实例化外部类, Spring 必须先处理其内部类的 Bean(因为可能在外部类中使用了内部类的实例作为字段)
  3. 由于内部类是非静态的, 所以要实例化内部类的 Bean, Spring 必须先有一个外部类的实例
  4. 这就形成了一个死循环: 外部类依赖于内部类, 内部类又依赖于外部类, 导致 Spring 无法完成 Bean 的创建

**而静态内部类具有独立性,**静态内部类 (static 修饰) 不依赖于外部类的实例, 它可以像普通类一样独立地创建.

因此, Spring 可以直接实例化静态内部类, 而不会引发循环依赖问题.

结论: 如果一个类交给 Spring 来管理(即作为 Spring Bean), 其中的 内部类 必须是 静态的!!

5.1.4 接口二: 校验用户输入

演示:

5.1.5 后端代码

java 复制代码
@Data
@Configuration
@ConfigurationProperties(prefix = "captcha")
public class CaptchaProperties {
    private int width;
    private int height;
    private Session session;
    @Data
    public static class Session {
        private String key;
        private String date;
    }
}
java 复制代码
@RestController
@RequestMapping("/captcha")
public class CaptchaController {
    @Autowired
    private CaptchaProperties captchaProperties;
    // 定义超时时间
    private static final long TIME = 30 * 60 * 1000;
    @RequestMapping("/getCaptcha")
    public void getCaptcha(HttpServletResponse response, HttpSession session) throws IOException {
        // 手动将响应的 Content-Type 设置为 图片格式, 以便浏览器进行解析
        response.setContentType("image/jpeg");
        // response.setCharacterEncoding("utf-8");
        // 设置无缓存形式(通常是由前端处理的)
//        response.setHeader("Pragma", "no-Cache");
        // 定义图形验证码的长、宽、验证码字符数、干扰线宽度
        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight());
        // 将验证码的值存到用户 Session 中
        session.setAttribute(captchaProperties.getSession().getKey(), captcha.getCode());
        // 存储验证码生成完毕(用户开始填写)时的时间戳
        session.setAttribute(captchaProperties.getSession().getDate(), new Date());
        // 直接把验证码写到浏览器
        captcha.write(response.getOutputStream());
    }

    /**
     * 验证输入的验证码是否正确
     * @param captcha 用户输入的验证码
     * @param session
     * @return true / false
     */
    @RequestMapping("/check")
    public boolean check(String captcha, HttpSession session) {
        // 从用户 session 中获取验证码的正确值
        String code = (String) session.getAttribute(captchaProperties.getSession().getKey());
        // 获取用户开始填写时的时间戳
        Date date = (Date) session.getAttribute(captchaProperties.getSession().getDate());
        // 验证输入值是否合法
        if (!StringUtils.hasLength(code)) {
            return false;
        }
        // 忽略大小写, 若输入正确 && 没有超时输入 && session 没有过期(data != null) => 验证通过
        if(code.equalsIgnoreCase(captcha) && date != null &&
                System.currentTimeMillis() - date.getTime() < TIME) {
            return true;
        }
        // 验证失败
        return false;
    }
}
java 复制代码
spring:
  application:
    name: spring-captcha-demo

captcha:
  width: 150
  height: 50
  session :
    key: CAPTCHA_SESSION_KEY
    date: CAPTCHA_SESSION_DATE

5.1.6 前端代码

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <!-- 将 get 请求设置为无缓存形式 -->
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
  <meta http-equiv="Pragma" content="no-cache" />
  <meta http-equiv="Expires" content="0" />

  <title>验证码</title>
  <style>
    #inputCaptcha {
      height: 30px;
      vertical-align: middle; 
    }
    #verificationCodeImg{
      vertical-align: middle; 
    }
    #checkCaptcha{
      height: 40px;
      width: 100px;
    }
  </style>
</head>

<body>
  <h1>输入验证码</h1>
  <div id="confirm">
    <input type="text" name="inputCaptcha" id="inputCaptcha">
    <img id="verificationCodeImg" src="/captcha/getCaptcha" style="cursor: pointer;" title="看不清?换一张" />
    <input type="button" value="提交" id="checkCaptcha">
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
  <script>
    
    $("#verificationCodeImg").click(function(){
      $(this).hide().attr('src', '/captcha/getCaptcha?dt=' + new Date().getTime()).fadeIn();
    });

    $("#checkCaptcha").click(function () {
        $.ajax({
          type: "post",
          url: "/captcha/check",
          data: {
            captcha: $("#inputCaptcha").val()
          },
          success: function(res) {
            if(res) {
              location.href = "success.html";
            }else {
              alert("验证码错误!!");
            }
          }

        });
    });

  </script>
</body>

</html>
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>验证成功页</title>
</head>
<body>
    <h1>验证成功</h1>
</body>
</html>

END

相关推荐
缺点内向40 分钟前
Java:创建、读取或更新 Excel 文档
java·excel
带刺的坐椅1 小时前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看3 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程3 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t3 小时前
ZIP工具类
java·zip
lang201509283 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan4 小时前
第10章 Maven
java·maven
百锦再5 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说5 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多5 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring