【问题标题】:Spring can't determine generic types when lambda expression is used instead of anonymous inner class当使用 lambda 表达式而不是匿名内部类时 Spring 无法确定泛型类型
【发布时间】:2014-09-07 15:27:57
【问题描述】:

我正在玩 Spring 的 ConversionService,添加一个简单的转换器来将 ZonedDateTime (Java 8) 转换为 String

@Bean
public ConversionServiceFactoryBean conversionServiceFactoryBean() {
    ConversionServiceFactoryBean conversionServiceFactoryBean =
        new ConversionServiceFactoryBean();

    Converter<ZonedDateTime, String> dateTimeConverter =
        new Converter<ZonedDateTime, String>() {
            @Override
            public String convert(ZonedDateTime source) {
                return source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
            }
        };

    conversionServiceFactoryBean.setConverters(
        new HashSet<>(Arrays.asList(dateTimeConverter)));
    return conversionServiceFactoryBean;
}

这很好用。但我的 IDE (IntelliJ) 建议用 lambda 表达式替换匿名内部类:

Converter<ZonedDateTime, String> dateTimeConverter =
    source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);

如果我这样做了,那么它就不再起作用了,我收到一个关于 Spring 无法确定泛型类型的错误:

Caused by: java.lang.IllegalArgumentException: Unable to the determine sourceType <S> and targetType <T> which your Converter<S, T> converts between; declare these generic types.
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at org.springframework.core.convert.support.GenericConversionService.addConverter(GenericConversionService.java:100)
    at org.springframework.core.convert.support.ConversionServiceFactory.registerConverters(ConversionServiceFactory.java:50)
    at org.springframework.context.support.ConversionServiceFactoryBean.afterPropertiesSet(ConversionServiceFactoryBean.java:70)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1627)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1564)

代表 lambda 表达式的 Class 对象显然与匿名内部类的 Class 有很大不同,Spring 无法再确定泛型类型。 Java 8 如何使用 lambda 表达式准确地做到这一点?这是 Spring 中可修复的错误,还是 Java 8 没有提供必要的信息?

我使用的是 Spring 4.1.0.RELEASE 和 Java 8 update 20。

【问题讨论】:

标签: java spring java-8


【解决方案1】:

This postlinkedin the comments by Alan Stokes很好地解释了这个问题。

基本上,在当前的JDK中,lambda的实际实现被编译到声明类中,JVM产生一个Lambda类,其方法是接口中声明的方法的擦除。

所以

Converter<ZonedDateTime, String> dateTimeConverter =
    source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);

产生一种合成方法,如

private static java.lang.String com.example.Test.lambda$0(java.time.ZonedDateTime source)  {
    return source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}

由生成的 lambda 类实例调用。在内部,函数式接口方​​法只是简单地转换为上述方法的参数类型。 JLS states

如果被覆盖的方法类型的擦除在其 删除U的函数类型的签名,然后在之前 评估或执行 lambda 主体,方法的主体检查 每个参数值都是一个子类或子接口的实例 函数类型中对应参数类型的擦除 U;如果没有,则抛出 ClassCastException

VM 本身会生成一个覆盖方法,该方法与接口中声明的方法原始等效。

关于类型的唯一信息在上面的static 方法中。由于此方法是声明类的一部分,因此在给定从 lambda 表达式生成的实例的情况下,Spring 无法检索它。

但是,你可以这样做

interface ZonedDateTimeToStringConverter extends Converter<ZonedDateTime, String> {
}

Converter<ZonedDateTime, String> dateTimeConverter = (ZonedDateTimeToStringConverter)
    source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);

ZonedDateTimeToStringConverter dateTimeConverter =  source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);

这会强制 lambda 声明一个类似的方法

public String convert(ZonedDateTime zdt);

Spring 将能够找到它并解析目标和源类型。

【讨论】:

  • 我想知道是否可以使用ASM来学习签名合成方法?
【解决方案2】:

查看TypeTools:

Converter<ZonedDateTime, String> converter = source -> source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(Converter.class, converter.getClass());

assert typeArgs[0] == ZonedDateTime.class;
assert typeArgs[1] == String.class;

此方法适用于 Oracle/OpenJDK,因为它使用 sun.reflect.ConstantPool API。

注意:我被要求将这项工作贡献给 Spring,但我还没有空闲时间来做这件事(快速浏览一下,干净地集成 Spring 现有的泛型类型解析的东西似乎并不简单)。

【讨论】:

  • 谢谢。如果你有机会,请把这个贡献给 Spring,这将非常有用。
  • @Jesper 我想 - 我现在只是忙于更高优先级的事情:)
  • 这是一个跟踪 Spring Framework 的票证。请注意,它仅适用于 Oracle JDK 的事实是我们不愿意添加对此的支持的原因之一:jira.spring.io/browse/SPR-13698
猜你喜欢
  • 2019-04-02
  • 1970-01-01
  • 2014-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多