如何在未知子类时反序列化实现接口的枚举(Spring Boot Starter)
解决Spring Boot Starter中BaseErrorCode枚举的通用反序列化问题
核心思路
既然确定所有BaseErrorCode的实现都是枚举类,我们可以在Starter中实现一个通用反序列化器,通过扫描classpath自动收集所有实现BaseErrorCode的枚举类型,反序列化时根据JSON中的字符串值匹配对应的枚举实例,完全不需要提前知晓具体的枚举实现类。
实现步骤
1. 编写通用反序列化器
这个反序列化器会接收扫描到的所有枚举类型,反序列化时遍历这些枚举,找到与JSON值匹配的实例:
import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.deser.std.StdDeserializer class BaseEnumCodeDeserializer(private val enumTypes: List<Class<out Enum<*>>>) : StdDeserializer<BaseErrorCode>(BaseErrorCode::class.java) { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BaseErrorCode { val codeValue = p.text // 遍历所有枚举类,查找匹配的枚举实例 return enumTypes.asSequence() .map { it.enumConstants as Array<BaseErrorCode> } .flatMap { it.asSequence() } .firstOrNull { it.name == codeValue } ?: throw ctxt.weirdStringException( codeValue, BaseErrorCode::class.java, "找不到匹配的错误码枚举值: '$codeValue'" ) } }
2. 在Starter中配置Jackson模块并扫描枚举类
通过ClassPathScanningCandidateComponentProvider扫描classpath下所有实现BaseErrorCode的枚举类,注册到Jackson的反序列化器中:
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter import org.springframework.core.type.filter.AssignableTypeFilter import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider import org.springframework.http.converter.json.Jackson2ObjectMapperBuilderCustomizer @Configuration class ErrorCodeJacksonConfig { @Bean fun baseErrorCodeDeserializer(): BaseEnumCodeDeserializer { val scanner = ClassPathScanningCandidateComponentProvider(false) // 过滤实现BaseErrorCode的类 scanner.addIncludeFilter(AssignableTypeFilter(BaseErrorCode::class.java)) // 进一步过滤出枚举类 scanner.addIncludeFilter(object : AbstractTypeHierarchyTraversingFilter(false, false) { override fun matchClassName(className: String): Boolean { return try { val clazz = Class.forName(className) clazz.isEnum && BaseErrorCode::class.java.isAssignableFrom(clazz) } catch (e: ClassNotFoundException) { false } } }) // 扫描所有包下的符合条件的枚举类 val enumTypes = scanner.findCandidateComponents("") .map { Class.forName(it.beanClassName) as Class<out Enum<*>> } return BaseEnumCodeDeserializer(enumTypes) } @Bean fun jackson2ObjectMapperBuilderCustomizer(): Jackson2ObjectMapperBuilderCustomizer { return Jackson2ObjectMapperBuilderCustomizer { builder -> // 为BaseErrorCode类型注册自定义反序列化器 builder.deserializerByType(BaseErrorCode::class.java, baseErrorCodeDeserializer()) } } }
方案优势
- 仅需修改Starter代码,使用该Starter的仓库无需任何额外配置或修改
- 自动适配所有实现
BaseErrorCode的枚举类,无需提前知晓具体枚举实现 - 保持原有序列化格式不变(JSON中错误码仍为枚举name,如
"FOO")
注意事项
- 确保各仓库中自定义错误码枚举的
name全局唯一,避免出现不同枚举类有相同name导致匹配错误的情况 - 扫描范围是整个classpath,若需限制扫描包路径,可将
scanner.findCandidateComponents("")中的空字符串改为指定包前缀(但需用户配置,不符合"无需修改各仓库"的要求,因此不推荐)
内容的提问来源于stack exchange,提问作者FeXseven
相关产品推荐
相关产品推荐

