Spring @ConfigurationProperties处理器无法为枚举键值Map生成正确元数据
我需要在Spring Boot应用中实现轻量级的基于属性的访问控制。背景是系统存在不同类型用户、带属性的不同资源及不同访问级别,需要动态评估资源访问权限,因此打算用枚举分离权限评估逻辑。
我希望将权限配置存储在application.yml中,方便配置且无需修改代码。尝试用@ConfigurationProperties实现,让配置具备自动补全和软验证功能——这个方式在其他属性上运行正常,但使用Map时出了问题。
我可以把键改成字符串再在配置类里映射,但Spring文档说明Map应该支持枚举,且元数据会正确生成,这让我很困惑。我已经正确配置了spring-boot-configuration-processor(对其他属性有效),也生成了元数据JSON。
MyConfig.kt
package example.myapp import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConstructorBinding @ConstructorBinding @ConfigurationProperties("my-app") data class MyConfig( val permissions: Map<Role, Map<ResourceId, Set<Permission>>>, val owner: Map<ResourceId, Set<Permission>> ) { enum class Role { OWNER, VIEWER, } enum class Permission { VIEW, EDIT, } enum class ResourceId { ARTICLE_TITLE, ARTICLE_BODY, } }
spring-configuration-metadata.json
{ "groups": [ { "name": "my-app", "type": "example.myapp.MyConfig", "sourceType": "example.myapp.MyConfig" } ], "properties": [ { "name": "my-app.owner", "type": "java.util.Map<example.myapp.MyConfig$ResourceId,? extends java.util.Set<? extends example.myapp.MyConfig.Permission>>", "sourceType": "example.myapp.MyConfig" }, { "name": "my-app.permissions", "type": "java.util.Map<example.myapp.MyConfig$Role,? extends java.util.Map<example.myapp.MyConfig.ResourceId,? extends java.util.Set<? extends example.myapp.MyConfig.Permission>>>", "sourceType": "example.myapp.MyConfig" } ], "hints": [] }
版本信息
- Spring Boot 2.7.1
- Kotlin 1.7.10
更新尝试
把Map<>改成HashMap<>似乎能避免键的错误,但对值无效,仍会出现误导性警告:Cannot resolve configuration property
修改后的代码:
@ConstructorBinding @ConfigurationProperties("my-app") data class MyConfig( val permissions: HashMap<Role, HashMap<ResourceId, HashSet<Permission>>>, val owner: HashMap<ResourceId, HashSet<Permission>> )
1. 提升枚举类为顶层类
Spring Boot配置处理器对嵌套在@ConfigurationProperties数据类中的枚举支持存在兼容性问题,将枚举移到类外部作为顶层类,能让处理器正确识别枚举类型:
package example.myapp import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConstructorBinding enum class Role { OWNER, VIEWER, } enum class Permission { VIEW, EDIT, } enum class ResourceId { ARTICLE_TITLE, ARTICLE_BODY, } @ConstructorBinding @ConfigurationProperties("my-app") data class MyConfig( val permissions: Map<Role, Map<ResourceId, Set<Permission>>>, val owner: Map<ResourceId, Set<Permission>> )
2. 恢复使用接口类型(Map/Set)
Spring Boot配置绑定优先支持接口类型,使用HashMap/HashSet等具体实现类会干扰元数据生成逻辑,换回Map和Set即可。
3. 补充配置元数据提示(可选)
如果IDE仍无法自动补全,可手动在spring-configuration-metadata.json中添加枚举值提示,帮助IDE识别合法配置项:
{ "groups": [ { "name": "my-app", "type": "example.myapp.MyConfig", "sourceType": "example.myapp.MyConfig" } ], "properties": [ { "name": "my-app.owner", "type": "java.util.Map<example.myapp.ResourceId,java.util.Set<example.myapp.Permission>>", "sourceType": "example.myapp.MyConfig", "hints": [ { "values": [ {"value": "ARTICLE_TITLE"}, {"value": "ARTICLE_BODY"} ] } ] }, { "name": "my-app.permissions", "type": "java.util.Map<example.myapp.Role,java.util.Map<example.myapp.ResourceId,java.util.Set<example.myapp.Permission>>>", "sourceType": "example.myapp.MyConfig", "hints": [ { "values": [ {"value": "OWNER"}, {"value": "VIEWER"} ] } ] } ], "hints": [] }
4. 验证application.yml格式
确保配置文件中枚举值使用正确的字符串形式,示例如下:
my-app: owner: ARTICLE_TITLE: [VIEW, EDIT] ARTICLE_BODY: [VIEW] permissions: OWNER: ARTICLE_TITLE: [VIEW, EDIT] ARTICLE_BODY: [VIEW, EDIT] VIEWER: ARTICLE_TITLE: [VIEW] ARTICLE_BODY: [VIEW]
内容的提问来源于stack exchange,提问作者aSemy

