有什么办法可以实例化这两个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仍可以与原始类和字符串名称(!)一起使用,这真让我大吃一惊...对此有什么解决方法吗?
答案 0 :(得分:2)
有什么解决方法吗?
不幸的是,我认为不是。问题在于Java中的泛型在已编译的字节码中不存在-它们仅在编译时用于类型检查,而仅在源代码中存在。因此,在运行时,当Spring Boot看到您的bean配置类时,它只会看到两个都创建一个Container的bean定义。除非您自己给它们提供标识符,否则它们之间不会有任何区别。
答案 1 :(得分:2)
这是一个完全有效的解决方案。我放弃了这种方法,但是我发布了它,以防有人觉得有用。
我做了一个自定义注释:
@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());
答案 2 :(得分:1)
自Spring Boot v2.1.0起可能。
该版本在parameterizedContainer
上引入了新字段ConditionalOnMissingBean
。
@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());
}