我的基本spring启动应用程序有一个端点,它接受一个字符串,并将其作为键从db中读取一个char(1)值,名称为namedparameterjdbctemplate。我的代码的简化版本:
应用程序.java
@SpringBootApplication
@EnableCaching
@EnableScheduling
@ComponentScan(basePackages = {"my.package"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
控制器.java
@RestController
@RequestMapping(path = "enpoint", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public MyController {
@Autowired
private NamedParameterJdbcTemplate namedJdbcTemplate;
@PostMapping("do")
public Response doStuff(@RequestBody Request request) {
return this.namedJdbcTemplate.queryForObject(
"SELECT ONE_CHAR as value FROM TABLE WHERE KEY = :KEY",
new MapSqlParameterSource("KEY", request.getKey()),
new BeanPropertyRowMapper(Response.class)
);
}
}
请求.java
public class Request {
private String key;
// ... constructor / getter / setters ...
}
响应.java
因为在db表中,列one_char只能有3个可能的值(a、b或c)。而不是只有一个“private string value;”的响应对象,我创造了一个只会说chars的髓鞘。
public class Response {
private MyEnum value;
// ... constructor / getter / setters ...
}
myenum.java文件
“a”、“b”和“c”不是枚举值的好名称,因此我将这些信息放在枚举中的“id”变量中。
public enum MyEnum implements EnumById {
GOOD_NAME("A"), NICE_NAME("B"), PERFECT_NAME("C");
private final String id;
// ... constructor / getter / setters ...
}
枚举ID.java
我创建了一个接口'enumbyid',这样我就有了一个通用的方法来处理具有相同问题的所有枚举(我要表示为枚举的值不能或不应该用作变量名)。
public interface EnumById {
String getId();
}
这个代码当然不起作用。当spring试图转换从db中读取的任何值时,默认的stringtoenumconverter按枚举名进行转换,而不是调用我的getid()方法(如预期的那样)。我必须创建自己的转换器。。。但自从:
enumbyid可以应用于多个枚举
我需要类引用来调用clazz.getenumconstants()
我不想为每个扩展enumbyid的枚举编写自定义转换器
... 我需要创建一个自定义转换器工厂
StringToEnumbydConverterFactory.java文件
public class StringToEnumByIdConverterFactory implements ConverterFactory<String, EnumById> {
private static class StringToEnumByIdConverter<T extends EnumById> implements Converter<String, T> {
private final T[] values;
public StringToEnumByIdConverter(final T[] values) {
this.values = values;
}
public T convert(final String source) {
for (final T value : values) {
if (source.equals(value.getId())) {
return value;
}
}
return null;
}
}
public <T extends EnumById> Converter<String, T> getConverter(final Class<T> targetType) {
return new StringToEnumByIdConverter<>(targetType.getEnumConstants());
}
}
网络配置.java
作为https://www.baeldung.com/spring-type-conversions 告诉我们我必须将我的转换器工厂添加到spring格式寄存器。。。所以我写了:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new StringToEnumByIdConverterFactory());
}
}
其中。。。。是错的。。。我的转换器将永远不会被调用,因为默认的字符串到枚举转换器具有优先级(因为它是在我的转换器之前添加到格式化程序注册表的)。
我的解决方案是删除默认的转换器(org.springframework.core.convert.support.stringtoenumconverterfactory),放入我的并再次添加默认的转换器,因此我的webconfig类现在是这样的:
网络配置.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.removeConvertible(String.class, Enum.class);
registry.addConverterFactory(new StringToEnumByIdConverterFactory());
registry.addConverterFactory(new StringToEnumConverterFactory());
}
}
这又错了,因为无论出于什么原因,stringtoenumconverterfactory都不是公共的(甚至连里面使用的conversionutils类都不是,为什么?stringtoenumconverterfactory甚至被宣布为最终版本)。。。所以我不得不在我的项目中复制这些代码。
现在,至少我的应用程序运行了,我对它进行了调试,并验证了在调用addformatters时注册表是否正确更新。
当我调用端点时,仍然调用默认的转换器。如果没有编写/使用/调用与转换器相关的代码,我会得到相同的异常:
org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'java.lang.String' to required type 'my.package.constants.Constants$MyEnum' for property 'value'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [my.package.constants.Constants$MyEnum] for value 'A'; nested exception is java.lang.IllegalArgumentException: No enum constant 'my.package.constants.Constants.MyEnum.A'
通过查看堆栈跟踪,我发现org.springframework.beans.typeconverterdelegate.convertifnecessary是问题所在。
我去调试,发现conversionservice使用的转换器与我调试webconfig类时看到的不同(类中的寄存器有~155个转换器,这里使用的conversionservice只有52个)。我可以清楚地看到默认的stringtoenumconverterfactory:
在哪里/如何我不能告诉正确的转换服务(有多个?)使用我的转换器?
更新:发现问题,但仍未解决
我用 new BeanPropertyRowMapper(Response.class)
它有自己的转换服务: private ConversionService conversionService = DefaultConversionService.getSharedInstance();
这绕过了任何spring魔法,直接得到了defaultconversionservice。。。为什么这是常态?要正确添加枚举转换,我必须创建自己的类来扩展beanpropertyrowmapper。。。就目前而言,这似乎极为复杂。我错过什么了吗?
更新:找到了一个“假的”解决方案(因为它非常丑陋)
在查询之前写了这个,现在转换工作。。。我把它移到某个地方,它只会被称为一次,仍然有效。
// this is what BeanPropertyRowMapper use
DefaultConversionService sharedInstance = (DefaultConversionService) DefaultConversionService.getSharedInstance();
// remove default String to Enum conversion
sharedInstance.removeConvertible(String.class, Enum.class);
// addd my converter factory
sharedInstance.addConverterFactory(new StringToEnumByCodeConverterFactory());
// add again all default converter back, since:
// - I removed only the String->Enum one
// - Converters are placed in a LinkedMap
// No duplicates are inserted, I only get that the default Enum converter is added AFTER my converter
DefaultConversionService.addDefaultConverters(sharedInstance);
// Now my converter has priority on the default Strin->Enum one and BeanPropertyRowMapper can use it
这又蠢又丑。。。这真的是欺骗beanpropertyrowapper使用我的转换器而不必创建一个全新类的唯一方法吗?
暂无答案!
目前还没有任何答案,快来回答吧!