如何使用lambda确定实现通用FunctionalInterface的Bean的类型参数?

前端之家收集整理的这篇文章主要介绍了如何使用lambda确定实现通用FunctionalInterface的Bean的类型参数?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
@H_404_0@

我有一个通用的功能界面:

@FunctionalInterface
public interface FeederFeed(T t);
}

还有一些bean为不同的Animal子类实现了这个接口.

@Configuration
public class Config {
  @Bean
  public FeederFeeder() {
    return dog -> dogService.FeedDog(dog);
  }
  @Bean
  public FeederFeeder() {
    return cat -> catService.FeedCat(cat);
  }
}

现在已经为这些bean注入了一个服务类,并给出了一个Animal实例.如何确定使用正确的Feeder bean?

@Service
public class PetStore {
  @Autowired
  private List<FeederFeeders;

  private void Feed(Animal animal) {
    //TODO: How to determine the correct Feeder from Feeders?
    FeederFeeder = ....
    correctFeeder.Feed(animal);
  }
}

我试过的事情:

我最初以为我会好好使用How to get a class instance of generics type T
但是我遇到的问题是使用l​​ambda函数实现了bean,当我调用GenericTypeResolver.resolveTypeArgument(Feeder.getClass(),Feeder.class)时返回的类型是Animal(!)

然后我尝试使用bean的匿名子类.然后GenericTypeResolver可以确定每个Feeder将提供的动物的特定类型.但IntelliJ正在尖叫着我应该为它创建一个lambda,其他人也会使用PetStore.

我在Feeder接口中添加了一个getAnimalClass()方法. IntelliJ停止尖叫.但它确实感觉非常笨拙.

我第一次得到一个我尚未喂食的动物实例,我尝试/捕捉使用每个候选喂食器,直到找到一个有效的.然后我记得结果以备将来使用.也觉得很笨拙.

所以我的问题:
这样做的正确方法是什么?

最佳答案
简短的回答

我担心没有真正干净的方式.由于类型被删除,你需要在某处保留类型信息,并且使用getAnimalClass()的第三个建议是一种方法(但是在你的问题中你不清楚如何在以后使用它).

我个人会摆脱lambda并将一个canFeed(动物)添加Feeder中以委派决定(而不是添加getAnimalClass()).这样,喂食者有责任知道它可以喂养什么动物.

所以类型信息将保存在Feeder类中,例如使用通过构造传递的Class实例(或者通过覆盖getAnimalClass(),如你所想的那样):

final ClassFeeder(Class

因此canFeed方法可以使用它:

public boolean canFeed(Animal animal) {
   return typeParameterClass.isAssignableFrom(animal.getClass());
}

这将使PetStore中的代码非常干净:

private Feeder FeederFor(Animal animal) {
    return Feeders.stream()
                  .filter(Feeder -> Feeder.canFeed(animal))
                  .findFirst()
                  .orElse((Feeder) unknownAnimal -> {});
}

替代答案

正如您所说,存储类型信息会使其变得笨拙.然而,您可以以另一种不那么严格的方式传递类型信息,而不会使馈送器混乱,例如依赖于您注入的Spring bean的名称.

假设您在地图中注入PetStore中的所有Feeders:

@Autowired
MapFeederFeederMap;

现在您有一个包含进纸器名称(并隐含它的类型)和相应的进纸器的地图. canFeed方法现在只是检查子字符串:

private boolean canFeed(String FeederName,Animal animal) {
    return FeederName.contains(animal.getClass().getTypeName());
}

您可以使用它从地图中获取正确的Feeder:

private Feeder FeederFor(Animal animal) {
    return FeederMap.entrySet().stream()
                    .filter(entry -> canFeed(entry.getKey(),animal))
                    .map(Map.Entry::getValue)
                    .findFirst()
                    .orElse((Feeder) unknownAnimal -> {});
}

深潜

lambda是干净简洁的,所以如果我们想在配置中保留lambda表达式呢.我们需要类型信息,因此第一次尝试可以将canFeed方法作为默认方法添加Feeder< T>上.接口:

default 

当然我们不能做A == T,并且由于类型擦除,没有办法比较泛型类型A和T.通常,你引用了the trick,只有在使用泛型超类型时才有效.您提到了Spring工具箱方法,但让我们看一下Java实现:

this.entityBeanType = ((Class) ((ParameterizedType) getClass()
    .getGenericSuperclass()).getActualTypeArguments()[0]);

由于我们有多个Feeder实现,其中Feeder< T>超级接口,您可能认为我们可以采用这种策略.但是,我们不能,因为我们在这里处理lambda而lambda并没有实现为匿名内部类(它们不是编译成类而是使用invokedynamic指令)而且我们放弃了输入信息.

回到绘图板,如果我们将它改为抽象类,并将其用作lambda.然而,这是不可能的,首席语言架构师Brian Goetz解释了为什么on the mailinglist

Before throwing this use case under the bus,we did some corpus analysis to found how often abstract class SAMs are used compared to
interface SAMs. We found that in that corpus,only 3% of the lambda
candidate inner class instances had abstract classes as their target.
And most of them were amenable to simple refactorings where you added
a constructor/factory that accepted a lambda that was
interface-targeted.

我们可以去创建工厂方法解决这个问题,但这又是笨拙的,让我们走得太远.

由于我们已经做到这一点,让我们把我们的lambda回来并尝试以其他方式获取类型信息. Spring的GenericTypeResolver没有带来预期的结果Animal,但是我们可以得到hacky并利用Type信息存储在字节码中的方式.

编译lambda时,编译器会插入一个指向LambdaMetafactory的动态调用指令和一个带有lambda主体的合成方法.常量池中的方法句柄包含泛型类型,因为我们的Config中的泛型是显式的.在运行时,ASM会生成一个实现功能接口的类.不幸的是,这个特定的生成类不存储通用签名,你不能使用反射来绕过擦除,因为它是使用Unsafe.defineAnonymousClass定义的.有一个a hack从Class.getConstantPool中获取信息,它使用ASM来解析和返回参数类型,但是这个hack依赖于未记录的方法和类,并且容易受到JDK中代码更改的影响.您可以自己破解它(通过粘贴来自引用的代码)或使用实现此方法的库,例如TypeTools. Other hacks也可以工作,例如向lambda添加序列化支持并尝试获取方法签名来自序列化表单的实例化接口.不幸的是,我还没有找到解决新的Spring api的类型信息的方法.

如果我们采用这种方法在我们的界面中添加默认方法,您可以保留所有代码(例如配置)和实际的Feeder-hack hack减少到:

default Feed(A animalToFeed) {
    ClassFeederType = TypeResolver.resolveRawArgument(Feeder.class,this.getClass());
    return FeederType.isAssignableFrom(animalToFeed.getClass());
}

PetStore保持干净:

@Autowired
private List<FeederFeeders;

public void Feed(Animal animal) {
    FeederFor(animal).Feed(animal);
}

private Feeder FeederFor(Animal animal) {
    return Feeders.stream()
                  .filter(Feeder -> Feeder.canFeed(animal))
                  .findFirst()
                  .orElse(unknownAnimal -> {});
}

所以不幸的是,没有简单的方法,我想我们可以安全地得出结论,我们检查了所有(或至少多个基本)选项.

猜你在找的Spring相关文章