Spring 注入通用类型

Spring Inject Generic Type

假设我们有这样一个界面:

public interface Validator<T> {
  boolean isValid(T data);
}

这是核心模块的一部分。多个应用程序可以使用同一个核心模块,通用 T 具有不同的值。一个示例实现是(来自应用程序的特定模块):

@Component
public class AppValidator implements Validator<String> {
  @Override
  public boolean isValid(String data) {
    return false;
  }
}

然后在控制器中(核心模块的一部分):

@RestController
public class ValidateController {
  @Autowired
  private Validator validator;

  @RequestMapping("/")
  public void index() {
    validator.validate("");
  }
}

IntelliJ 抱怨我使用的是原始类型;如您所见,我实际上是在控制器中这样做的。

我的问题:有没有办法以有界的方式注入依赖项(而不是注入 Validator、注入 Validator<String>)?但是当然,绑定类型可能会根据使用核心模块的应用程序而改变?

如果不可能(可能是由于类型擦除),最好的做法是什么?难道只是为了使用Object?有没有更好的替代方案仍然提供类型安全?

我在某处看到有人说可以在编译时做一些魔术来改变类型,但我不确定如何,或者即使我没看错?

我正在使用 Spring,所以我希望 Spring 可以提供一些帮助!欢迎施展魔法!

答案很简单:您不需要任何魔法,它只在 Spring 中起作用。你有你的 AppValidator 然后你就这样做(它是通过查看泛型类型注入的):

@Autowired 
private Validator<String> appValidator;

没关系,现在假设您有两个 Validator<String>,然后呢? required a single bean but found two exception - 就是这样。这就是为什么这是一种可怕的做法,永远不要这样做。

在我的工作中,一个人创建了具有 3 个泛型类型的泛型接口,然后基于这些泛型类型进行注入,人们仍然讨厌他。它看起来像这样,是的,只要您在多个实现中没有以完全相同的顺序使用完全相同的 3 个泛型类型,它就可以工作:

@Autowired
private Invoker<String, Integer, Person> personInvoker;

@Autowired
private Invoker<Integer, String, Animal> animalInvoker;

即使您的代码中没有多个 Validator<String>,并且您不打算拥有更多 - 其他人可能会加入并添加它们,或者许多其他情况。

这里是你的模块(应用程序和核心)之间的关系:

Application 1       Application 2      Application 3
     |                   |                   |
Validator<Foo>     Validator<Bar>     Validator<FooBar>
     |                   |                   |  
     |                   |                   |  
     |__ __ __ __ __ __ _| __ __ __ __ __ __ | 
                         |
                         | <<uses>>
                         |
                        \ /
                     Core Module    
                         |
                 ValidateController  (not generic rest controller)                   

这里有问题,因为您希望共享组件 ValidateController 依赖于特定的通用应用程序 Validator class 但 ValidateController 不是通用的 class,所以你只能坚持使用 Object 作为通用类型,你将在其中使用 Validator 字段。
为了使事情保持一致,您应该创建这个缺失的 link。 事实上,您需要控制器的不同子classes,因为每个控制器都需要使用特定的验证器实例。
例如,您可以在 shared/code 模块中定义一个抽象 class/interface ValidateController 并让每个 subclass 扩展它并定义自己为通用 Validator class 使用。

这里是你的模块之间的目标关系:

Application 1        Application 2        Application 3
     |                   |                      |
Validator<Foo>       Validator<Bar>       Validator<FooBar>
FooController(bean)  BarController(bean)  FooBarController(bean)
     |                   |                      |  
     |                   |                      |  
     |__ __ __ __ __ ___ | __ ___ __ __ __ __ __| 
                         |
                         | <<uses>>
                         |
                        \ /
                     Core Module    
                         |
                 ValidateController<T>  (abstract class and not a bean)                   

例如在 core/shared 模块中:

public abstract class ValidateController<T> {

  private Validator<T> validator;

  ValidateController(Validator<T> validator){
     this.validator = validator;
  }

  @RequestMapping("/")
  public void index(T t) {
    boolean isValid = validator.validate(t);
  }

}

在应用程序中,定义您的验证器实现:

@Component
public class AppValidator implements Validator<String> {
  @Override
  public boolean validate(String data) {
    return ...;
  }
}

并定义 StringController subclass(或 @Bean 作为替代)以设置正确的 Validator :

@RestController
public class StringController extends ValidateController<String>{

   public ValidateControllerApp(Validator<String> validator){
       this.validator = validator;
   }

}