在开发 OA 系统并配置 Sa-Token 权限拦截白名单时,需要将免登录路径(如 Swagger 文档、登录接口等)提取到
application.yml中统一管理。原本以为是一个简单的属性注入,却触发了 Spring Boot 配置解析的一个经典陷阱。
💥 踩坑现场
1. 配置文件 (application.yml)
为了保持配置的层级清晰,使用了 YAML 标准的短横线(-)数组语法:
secure:
ignore:
urls:
- /auth/**
- /doc.html
- /webjars/**
2. Java 代码
在配置类中,习惯性地使用 @Value 注解试图将这个数组直接注入到 List<String> 中:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${secure.ignore.urls}")
private List<String> ignoreUrls;
// ... 拦截器配置代码
}
3. 启动报错
项目启动直接崩溃,控制台抛出如下异常:
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'secure.ignore.urls' in value "${secure.ignore.urls}"
🔍 原因深度分析:为什么 @Value 找不到键?
这并不是 @Value 不支持 List,而是 Spring Boot 底层解析 YAML 数组的机制与 @Value 的精准匹配逻辑产生了冲突。
Spring 是如何处理 YAML 数组的?
当你在 YAML 中使用 - 定义数组时,Spring Boot 在启动加载这个文件时,会把这个层级的结构“拍平”,转换成带索引的键值对存入内存中。也就是说,内存里实际存储的键名并不是你想的那个整体,而是:
secure.ignore.urls[0] = /auth/**secure.ignore.urls[1] = /doc.htmlsecure.ignore.urls[2] = /webjars/**
@Value 的匹配机制
@Value("${secure.ignore.urls}") 执行的是精准名称匹配。它拿着 "secure.ignore.urls" 这个字符串去内存里精确查找对应的键。 它翻遍了所有的键,发现只有带索引的 [0]、[1] 家族成员,根本没有一个叫做 secure.ignore.urls 的“光杆司令”键。因为找不到完全匹配的键,Spring 判定配置缺失,从而抛出 Could not resolve placeholder 异常。
🛠️ 优雅的解决方案
针对这种 YAML 数组结构,有两种规范的破局方式:
方案一:改为逗号分隔的单行字符串(适合简单场景)
如果不想改动配置结构,可以通过英文逗号将路径连成一个普通的字符串。Spring 内部的类型转换器非常聪明,检测到目标类型是 List 时,会自动按逗号进行切割并封装。
修改 YAML:
secure:
ignore:
urls: /auth/**,/doc.html,/webjars/**,/swagger-ui.html
注:Java 代码保持 @Value("${secure.ignore.urls}") 不变,直接启动即可成功注入。
方案二:改用 @ConfigurationProperties(企业级推荐标准)
如果白名单路径较多,坚持使用 YAML 的短横线 - 数组格式,则必须放弃 @Value,改用 @ConfigurationProperties。它采用“松散绑定(Relaxed Binding)”机制,天生能够识别带索引的复杂数组结构。
修改 Java 代码:
@Configuration
@Setter // 使用 Lombok 自动生成 setter 方法,否则无法注入
@ConfigurationProperties(prefix = "secure.ignore") // 核心前缀
public class WebMvcConfig implements WebMvcConfigurer {
// 变量名必须和 YAML 最后一级的名称保持一致 (对应 urls)
private List<String> urls;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/**")
.excludePathPatterns(urls); // 直接使用注入的 urls
}
}
💡 开发总结
@Value的局限性:适合读取单值(String、int 等)或逗号分隔的简单一维列表,它要求在环境变量中存在一个完全同名的精确键。@ConfigurationProperties的优势:是处理 YAML 复杂嵌套结构、Map 以及标准短横线-数组的首选方案,符合面向对象的配置管理规范。