Spring Boot #ConditionalOnMissingBean and generic types - java

Is there any way to get both of these beans instantiated:
#Bean
#ConditionalOnMissingBean
public Container<Book> bookContainer() {
return new Container<>(new Book());
}
#Bean
#ConditionalOnMissingBean
public Container<Computer> computerContainer() {
return new Container<>(new Computer());
}
#ConditionalOnMissingBean only takes the bean class into account, falsely making bookContainer and computerContainer the same type, thus only one of them gets registered.
I could give explicit qualifiers to each, but since this is a part of a Spring Boot Starter, it would make it annoying to use, as the user would then be forced to somehow know the exact name to override instead of the type only.
Potential approach:
Since it is possible to ask Spring for a bean by its full generic type, I might be able to implement a conditional factory that will try to get a fully-typed instance and produce one if it doesn't already exist. I'm now investigating if/how this can be done.
Amazingly, when implementing a custom condition (to be used with #Conditional), it does not have access to the bean type...
While every other modern injection framework operates on full types, Spring somehow still works with raw classes and string names(!) which just blows my mind... Is there any workaround for this?

It's possible since Spring Boot v2.1.0.
That version introduced the new field parameterizedContainer on 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());
}

Update: Spring now supports this natively. See the accepted answer.
Original answer:
Here's a fully working solution. I gave up on this approach, but I'm posting it in case someone finds it useful.
I made a custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
#Conditional(MissingGenericBeanCondition.class)
public #interface ConditionalOnMissingGenericBean {
Class<?> containerType() default Void.class;
Class<?>[] typeParameters() default {};
}
And a custom condition to go with it:
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");
}
}
These allow me to do the following:
#Bean
#ConditionalOnMissingGenericBean
public Container<Book> bookContainer() {
return new Container<>(new Book());
}
#Bean
#ConditionalOnMissingGenericBean
public Container<Computer> computerContainer() {
return new Container<>(new Computer());
}
Both beans will now be loaded as they're of different generic types. If the user of the starter would register another bean of Container<Book> or Container<Computer> type, then the default bean would not be loaded, exactly as desired.
As implemented, it is also possible to use the annotation this way:
#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());
}
Notes:
Loading the configuration class in the condition might not be safe, but in this specific case it also might be... I have no clue. Didn't bother investigating further as I settled on a different approach altogether (different interface for each bean). If you have any info on this, please comment.
If explicit types are provided in the annotation, e.g.
#Bean
#ConditionalOnMissingGenericBean(containerType=Container.class, typeParameters=Computer.class)
public Container<Computer> computerContainer() {
return new Container<>(new Computer());
}
this concern goes away, but this approach will not work if the type parameters are also generic, e.g. Container<List<Computer>>.
Another way to make this certainly safe would be to accept the declaring class in the annotation:
#ConditionalOnMissingGenericBean(declaringClass=CustomConfig.class)
and then use that instead of
Class<?> declaringClass = ClassUtils.forName(methodMeta.getDeclaringClassName(), context.getClassLoader());

Is there any workaround for this?
Unfortunately, I think not. The problem is that generics in Java do not exist in the compiled bytecode - they are used only for type checking at compile time and they only exist in the source code. Therefore at runtime, when Spring Boot sees your bean configuration class, it only sees two bean definitions that both create a Container. It does not see any difference between them unless you provide it yourself by giving them identifiers.

Related

Is it possible to create a custom condition equivalate of #ConditionalOnMissingBean with Spring (no spring-boot)?

I am trying to implement a Spring condition that will load my bean only if there is no other beans for a certain class.
The desired behavior is similar to "#ConditionalOnMissingBean" but without using spring-boot.
I am using Spring versions 5.3.13.
Is that possible? thanks.
I found a solution by digging around spring-boot source code.
ConditionalOnBean will only work on beans defined inside a configuration (bean methods).
This is also recommended by spring-boot java doc for #ConditionalOnBean
The condition can only match the bean definitions that have been processed by the
application context so far and, as such, it is strongly recommended to use this
condition on auto-configuration classes only. If a candidate bean may be created by
another auto-configuration, make sure that the one using this condition runs after.
Here is the basics of the solution I came up with, this may be improved but the basics operate well.
The condition:
#Slf4j
class MissingBeanCondition implements ConfigurationCondition {
#Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
var targetBeanType = metadata.getAnnotations()
.get(ConditionalOnMissingBean.class)
.getValue("value", Class.class)
// TODO throw a more informative error
.orElseThrow(() -> new RuntimeException("Failed to evaluate MissingBeanCondition"));
try {
context.getBeanFactory().getBean(targetBeanType);
} catch (NoSuchBeanDefinitionException e) {
return true;
}
return false;
}
}
The annotation:
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
#Conditional(MissingBeanCondition.class)
public #interface ConditionalOnMissingBean {
Class<?> value();
}
Usage example:
#Bean
#Singleton
#ConditionalOnMissingBean(Provider.class)
public Provider myClass() {
return new DefaultProvider();
}

Spring classpath component scanning

I need to build mappings for classes (literally a Map<Class<?>, String>), which won't vary at runtime, and keeping things decoupled is a priority. Since I'm in a Spring application, I thought I'd use an annotation and ClassPathScanningCandidateComponentProvider more or less like so:
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Mapping {
String value();
}
And:
public class MappingLookUp {
private static final Map<Class<?>, String> MAPPING_LOOK_UP;
static {
Map<Class<?>, String> lookUp = new HashMap<>();
ClassPathScanningCandidateComponentProvider scanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);
scanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Mapping.class));
for (BeanDefinition beanDefinition : scanningCandidateComponentProvider.findCandidateComponents("blah")) {
Class<?> clazz;
try {
clazz = Class.forName(beanDefinition.getBeanClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Mapping mapping = AnnotationUtils.getAnnotation(clazz, Mapping.class);
if (mapping == null) {
throw new IllegalStateException("This should never be null");
}
lookUp.put(clazz, mapping.value());
}
MAPPING_LOOK_UP = Collections.unmodifiableMap(lookUp);
}
public static String getMapping(Class<?> clazz) {
...
}
}
Although I believe this will work, this feels like:
a lot to put in a static initialization
a hacky use of the scanning component provider, even though it's commonly recommended for this purpose; BeanDefinition makes it sound like it's intended for finding Spring beans rather than general class definitions.
To be clear, the annotated values are data classes -- not Spring-managed beans -- so a BeanPostProcessor pattern doesn't fit, and indeed, that's why it feels awkward to use the scanning component provider that, to me, seems intended for discovery of Spring managed beans.
Is this the proper way to be implementing this pattern? Is it a proper application of the provider? Is there a feasible alternative without pulling in other classpath scanning implementations?
I will suggest this doesn't look like it is done in a very Spring-y way.
If I were to be doing this, I would utilize Spring's BeanPostProcessor or BeanFactoryPostProcessor. Both of these allow for introspection on all Bean's in Spring's BeanFactory, and would allow you to get away from the static-ness of your current setup, as the PostProcessors are just Spring Bean's themselves.
class MappingLookup implements BeanPostProcessor {
private final Map<Class<?>, String> lookup = new HashMap<>();
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// check bean's class for annotation...
// add to lookup map as necessary...
// make sure to return bean (javadoc explains why)
return bean;
}
public String getMapping(Class<?> clazz) {
// ...
}
// omitted other methods...
}
I asked a very similar question recently How to get list of Interfaces from #ComponentScan packages and finally implemented the first of suggested approaches.
You can see the code https://github.com/StanislavLapitsky/SpringSOAProxy see https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/service/ProxyableScanRegistrar.java and of course initialization annotation https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/annotation/ProxyableScan.java the key thing is to add #Import({ProxyableScanRegistrar.class})
The key code is
public class ProxyableScanRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// Get the ProxyableScan annotation attributes
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ProxyableScan.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("value");
if (basePackages.length == 0) {
// If value attribute is not set, fallback to the package of the annotated class
basePackages = new String[]{((StandardAnnotationMetadata) metadata).getIntrospectedClass().getPackage().getName()};
}

In Spring 4, how do I define generic beans without explicitly specifying every possible type?

In Spring 4, it's possible to use generic types to distinguish between the types of beans. I've read the basic documentation describing this feature.
The problem is that (as far as I can tell) this requires an explicit bean definition for every possible generic type you might want to use. However, if there's a lot of different classes that can realistically be used here, that will become painful very quickly (and if it's a generic with two or three type parameters, imagine how quickly this could become unmanageable). An example:
#Bean
public SomeGeneric<ThingA> someGenericThingA(ThingA thingA) {
return new SomeGeneric<>(thingA);
}
#Bean
public SomeGeneric<ThingB> someGenericThingB(ThingB thingB) {
return new SomeGeneric<>(thingB);
}
#Bean
public SomeGeneric<ThingC> someGenericThingC(ThingC thingC) {
return new SomeGeneric<>(thingC);
}
#Bean
public SomeGeneric<ThingD> someGenericThingD(ThingD thingD) {
return new SomeGeneric<>(thingD);
}
#Bean
public SomeGeneric<ThingE> someGenericThingE(ThingE thingE) {
return new SomeGeneric<>(thingE);
}
#Bean
public SomeGeneric<ThingF> someGenericThingF(ThingF thingF) {
return new SomeGeneric<>(thingF);
}
Once the above is set up, I can very conveniently autowire any of these instances, as in the following:
#Autowired
SomeGeneric<ThingC> someGenericThingC;
But because this is such a mindless, repetitive pattern, I was wondering whether there is some way to avoid all this repetition in the configuration class. Could I use some kind of Bean Factory or postprocessor to reduce this repetition?

Autowiring Class<T> for any T

So, I have some generic components that use reflection to initialize themselves and by doing so, they require Class<T> objects at instanciation time. Those components use annotations in order to generate useful metadata and/or convert the object to another representation more appropriate for the task at hand.
I reduced my issue down to this sample component :
#Component
public class Instantiator<T> {
final Class<T> klass;
#Autowired
public Instantiator(Class<T> klass) {
this.klass = klass;
}
public T instantiate() {
try {
return klass.newInstance();
} catch (InstantiationException|IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
Spring does not know how to automatically inject Class<T> instances, so I tried writing the following boilerplate code for each T for which I want Class<T> to be available.
#Bean
Class<Instantiatee> instantiateeClass() {
return Instanciatee.class;
}
It does not work.
Spring since version 4 has support for Autowiring generic types, but in my case, it has to infer what T is assigned to in Class<T>. Since by default Spring creates singleton beans and therefore could not possibly infer an appropriate T, I tried adding #Scope("prototype") but I ended up with a ClassCastException since the container does not know how to infer T anyway.
So, I removed the #Component annotation from Instantiator and settled on this workaround for each T I have :
#Bean
Instantiator<Instantiatee> instantiator() {
return new Instantiator<>(Instantiatee.class);
}
Do you know a workaround to make this work so that T will be inferred each time I want an Instantiator or another generic component depending on it ?
FYI, we are using spring 4.1.4 with boot.
I posted a more complete sample there : https://gist.github.com/anonymous/79e1a7ebe7c25c00a6c2.
Defining beans with #Bean, you give the bean name in the method name by default - in your case getInstanciateeClass. Also, when autowiring the default bean name is considered the parameter name, in your case klass. Because of this, Spring cannot match the beans, since they have different names and most probably there are more than one Class instances in the ApplicationContext. It does not matter if one is Class<Foo> and another one is Class<Bar>, Spring sees them as Class so it cannot do autowiring by type.
You can fix this by using the same default name both when defining the bean and when autowiring it.
#Autowired
public Instanciator(Class<T> klass) {
this.klass = klass;
}
#Bean
Class<Instanciatee> klass() {
return Instanciatee.class;
}
You can also specify the name of the bean in the #Bean annotation:
#Autowired
public Instanciator(Class<T> klass) {
this.klass = klass;
}
#Bean(name = "klass")
Class<Instanciatee> getInstanciateeClass() {
return Instanciatee.class;
}
Or you can also give the bean name when autowiring:
#Autowired
#Qualifier("getInstanciateeClass")
public Instanciator(Class<T> klass) {
this.klass = klass;
}
#Bean
Class<Instanciatee> getInstanciateeClass() {
return Instanciatee.class;
}

Referencing a bean with dependencies using Spring Java Config

In the following Spring Java Config:
#Configuration
#EnableAutoConfiguration
#ComponentScan("my.package")
public class Config {
#Bean
public BasicBean basicBean1() {
return new BasicBean("1");
}
#Bean
public BasicBean basicBean2() {
return new BasicBean("2");
}
#Bean
public ComplexBean complexBeanByParameters(List<BasicBean> basicBeans) {
return new ComplexBean(basicBeans);
}
#Bean
public ComplexBean complexBeanByReferences() {
return new ComplexBean(Arrays.asList(basicBean1(), basicBean2()));
}
}
I can create two ComplexBeans using either parameter injection, which is elegant, but has shortcomings if a have a few other beans of BasicBean type and only want a few (the parameters can of course be of type BasicBean and enumerate by name the beans I'm interested of, but it could turn out to be a very long list, at least for arguments sake). In case I wish to reference the beans directly I might use the complexBeanByReferences style, which could be useful in case of ordering or some other consideration.
But say I want to use the complexBeanByReference style to reference the bean complexBeanByParameters, that is something along the line of:
#Bean
public ComplexBeanRegistry complexBeanRegistry() {
return new ComplexBeanRegistry(
Arrays.asList(
complexBeanByParameters(), // but this will not work!
complexBeanByReferences()
)
);
}
How would I reference complexBeanByParameters, without having to specify a list of dependencies to complexBeanRegistry? Which, the latter in all honesty should be completely oblivious of.
There is the option to just use
public ComplexBeanRegistry complexBeanRegistry(List<ComplexBeans> complexBeans) {...}
of course, but this might not be an option in certain cases, specifically when using the CacheConfigurer from spring-context. In this case the Java Config is intended to
create the beans
by implementing CacheConfigurer, override the default instances of the CacheManager and KeyGenerator beans.
The requirement to implement CacheConfigurer means I can't change the signature to use parameter injection.
So the question is, is there a way to reference complexBeanByParameters using the "direct" reference style?
Maybe you could reference it with separation by Qualifier:
#Bean
#Qualifier("complexBeanParam")
public ComplexBean complexBeanByParameters(List<BasicBean> basicBeans) {
return new ComplexBean(basicBeans);
}
#Bean
#Qualifier("complexBeanRef")
public ComplexBean complexBeanByReferences() {
return new ComplexBean(Arrays.asList(basicBean1(), basicBean2()));
}
and for example autowire:
#Autowired
#Qualifier("complexBeanParam")
private ComplexBean beanParam;

Categories

Resources