![](/img/trans.png)
[英]Is it possible to create a custom condition equivalate of @ConditionalOnMissingBean with Spring (no spring-boot)?
[英]Spring Boot @ConditionalOnMissingBean and generic types
有沒有辦法讓這兩個bean實例化:
@Bean
@ConditionalOnMissingBean
public Container<Book> bookContainer() {
return new Container<>(new Book());
}
@Bean
@ConditionalOnMissingBean
public Container<Computer> computerContainer() {
return new Container<>(new Computer());
}
@ConditionalOnMissingBean
只考慮 bean 類,錯誤地使bookContainer
和computerContainer
為同一類型,因此只有其中一個被注冊。
我可以為每個指定顯式限定符,但由於這是 Spring Boot Starter 的一部分,因此使用起來會很煩人,因為用戶將被迫以某種方式知道要覆蓋的確切名稱,而不僅僅是類型。
潛在的方法:
由於可以通過完整的泛型類型向 Spring 請求 bean,因此我可能能夠實現一個條件工廠,該工廠將嘗試獲取一個完整類型的實例並在它不存在時生成一個。 我現在正在調查是否/如何做到這一點。 令人驚訝的是,在實現自定義條件(與@Conditional
一起使用)時,它無法訪問 bean 類型......
雖然其他所有現代注入框架都在完整類型上運行,但 Spring 仍然以某種方式使用原始類和字符串名稱(!),這讓我大吃一驚......有什么解決方法嗎?
更新: Spring 現在原生支持此功能。 請參閱已接受的答案。
這是一個完全有效的解決方案。 我放棄了這種方法,但我將其發布以防有人發現它有用。
我做了一個自定義注釋:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(MissingGenericBeanCondition.class)
public @interface ConditionalOnMissingGenericBean {
Class<?> containerType() default Void.class;
Class<?>[] typeParameters() default {};
}
以及與之配套的自定義條件:
public class MissingGenericBeanCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (!metadata.isAnnotated(ConditionalOnMissingGenericBean.class.getName()) || context.getBeanFactory() == null) {
return false;
}
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnMissingGenericBean.class.getName());
Class<?> containerType = (Class<?>) attributes.get("containerType");
Class<?>[] typeParameters = (Class<?>[]) attributes.get("typeParameters");
ResolvableType resolvableType;
if (Void.class.equals(containerType)) {
if (!(metadata instanceof MethodMetadata) || !metadata.isAnnotated(Bean.class.getName())) {
throw error();
}
//When resolving beans within the starter
if (metadata instanceof StandardMethodMetadata) {
resolvableType = ResolvableType.forType(((StandardMethodMetadata) metadata).getIntrospectedMethod().getGenericReturnType());
} else {
//When resolving beans in an application using the starter
MethodMetadata methodMeta = (MethodMetadata) metadata;
try {
// This might not be a safe thing to do. See the notes below.
Class<?> declaringClass = ClassUtils.forName(methodMeta.getDeclaringClassName(), context.getClassLoader());
Type returnType = Arrays.stream(declaringClass.getDeclaredMethods())
.filter(m -> m.isAnnotationPresent(Bean.class))
.filter(m -> m.getName().equals(methodMeta.getMethodName()))
.findFirst().map(Method::getGenericReturnType)
.orElseThrow(MissingGenericBeanCondition::error);
resolvableType = ResolvableType.forType(returnType);
} catch (ClassNotFoundException e) {
throw error();
}
}
} else {
resolvableType = ResolvableType.forClassWithGenerics(containerType, typeParameters);
}
String[] names = context.getBeanFactory().getBeanNamesForType(resolvableType);
return names.length == 0;
}
private static IllegalStateException error() {
return new IllegalStateException(ConditionalOnMissingGenericBean.class.getSimpleName()
+ " is missing the explicit generic type and the implicit type can not be determined");
}
}
這些允許我執行以下操作:
@Bean
@ConditionalOnMissingGenericBean
public Container<Book> bookContainer() {
return new Container<>(new Book());
}
@Bean
@ConditionalOnMissingGenericBean
public Container<Computer> computerContainer() {
return new Container<>(new Computer());
}
現在將加載這兩個 bean,因為它們屬於不同的泛型類型。 如果啟動器的用戶將注冊另一個Container<Book>
或Container<Computer>
類型的 bean,則不會完全按照需要加載默認 bean。
在實現時,也可以這樣使用注釋:
@Bean
//Load this bean only if Container<Car> isn't present
@ConditionalOnMissingGenericBean(containerType=Container.class, typeParameters=Car.class)
public Container<Computer> computerContainer() {
return new Container<>(new Computer());
}
注意:在條件中加載配置類可能不安全,但在這種特定情況下它也可能是......我不知道。 由於我完全采用了不同的方法(每個 bean 的接口不同),因此沒有進一步調查。 如果您有這方面的任何信息,請發表評論。
如果在注釋中提供了顯式類型,例如
@Bean
@ConditionalOnMissingGenericBean(containerType=Container.class, typeParameters=Computer.class)
public Container<Computer> computerContainer() {
return new Container<>(new Computer());
}
這種擔憂消失了,但是如果類型參數也是通用的,例如Container<List<Computer>>
,這種方法將不起作用。
使這當然安全的另一種方法是接受注釋中的聲明類:
@ConditionalOnMissingGenericBean(declaringClass=CustomConfig.class)
然后用它代替
Class<?> declaringClass = ClassUtils.forName(methodMeta.getDeclaringClassName(), context.getClassLoader());
有什么解決方法嗎?
不幸的是,我認為不是。 問題是 Java 中的泛型不存在於編譯的字節碼中——它們僅用於編譯時的類型檢查,並且只存在於源代碼中。 因此,在運行時,當 Spring Boot 看到您的 bean 配置類時,它只會看到兩個都創建了 Container 的 bean 定義。 它看不出它們之間有任何區別,除非您自己通過為它們提供標識符來提供它。
從 Spring Boot v2.1.0 開始就有可能。
該版本在ConditionalOnMissingBean
上引入了新字段parameterizedContainer
。
@Bean
@ConditionalOnMissingBean(value = Book.class, parameterizedContainer = Container.class)
public Container<Book> bookContainer() {
return new Container<>(new Book());
}
@Bean
@ConditionalOnMissingBean(value = Computer.class, parameterizedContainer = Container.class)
public Container<Computer> computerContainer() {
return new Container<>(new Computer());
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.