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;
}
}
假设我们有这样一个界面:
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;
}
}