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;
}
Related
Let's suppose I have a Wrapper with generic type:
#Component
#Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class Wrapper<T> {
private final Class<T> wrappedClass;
public Wrapper(Class<T> wrappedClass) {
this.wrappedClass = wrappedClass;
}
}
And I want to use this Wrapper with many classes (for example > 100). Is it possible to make Spring create singleton of wrapper for each generic type and pass generic class as parameter to constructor? For example, Spring must always inject the same instance of Wrapper<Foo>. If it is possible, please give example with java code configuration, but not with xml.
If I understood correctly you want to add beans of wrapper dynamically based on some criteria that some beans (like Foo / Bar) adhere to and some don't.
This is a kind of advanced stuff in spring, but in a nutshell you will have to implement a Bean Factory Post Processor that will be called automatically by spring during the startup.
This is a point where you could analyze the beans by iterating over all the "accessible" beans (like Foo / Bar and others) and for beans that should be wrapped you will create a bean definition of the wrapper, despite the fact that the wrapper itself is not a bean.
I've created a simple example to illustrate this. In my sample project I've put everything under package "wrappers":
#Wrappable
public class Foo {
}
#Wrappable
public class Bar {
}
public class ShouldNotBeWrapped {
}
Note that I've put an annotation #Wrappable - a custom annotation that will serve as a "differentiator" of what should be wrapped and what not. The processing of the annotation will be done in Bean Factory Post Processor.
The annotation is nothing special really, it should be acessible in runtime (spring is a runtime framework and be put on classes):
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface Wrappable {
}
The java config will add Foo, Bar, ShouldNotBeWrapped as beans and also Bean Factory Post Processor that I'll describe below:
#Configuration
public class WrappersJavaConfig {
#Bean
public Foo foo () {
return new Foo();
}
#Bean
public Bar bar () {
return new Bar();
}
#Bean
public ShouldNotBeWrapped shouldNotBeWrapped () {
return new ShouldNotBeWrapped();
}
#Bean
public WrappersEnrichmentBFPP wrappersEnrichmentBFPP () {
return new WrappersEnrichmentBFPP();
}
}
The Wrapper class itself for the sake of example has toString but it doesn't differ much from your wrapper presented in the question:
public class Wrapper<T> {
private T wrapped;
public Wrapper(T wrapped) {
this.wrapped = wrapped;
}
#Override
public String toString() {
return "Wrapper for" + wrapped;
}
}
And the Main class will list all the loaded beans and get their classes + call toString so that we could see that the wrappers are defined correctly:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(WrappersJavaConfig.class);
String[] names = ctx.getBeanDefinitionNames();
for(String name : names) {
Object bean = ctx.getBean(name);
if(bean.getClass().getPackage().getName().startsWith("wrappers")) {
System.out.println(ctx.getBean(name).getClass() + " ==> " + ctx.getBean(name));
}
}
}
}
Sidenote, the "if" condition in the main method is because I don't want to print the beans that spring loads by itself (infra stuff, etc) - only my beans which all reside in package "wrappers" as I've mentioned above.
Now the BeanFactoryPostProcessor - is a regular bean in a sense that it gets registered in the java config and it looks like this (your implementation might be different but the idea is the same):
public class WrappersEnrichmentBFPP implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] bddNames = beanFactory.getBeanDefinitionNames();
for(String bddName : bddNames) {
Object bean = beanFactory.getBean(bddName);
if(bean.getClass().isAnnotationPresent(Wrappable.class)) {
BeanDefinition wrappedBdd = BeanDefinitionBuilder.genericBeanDefinition(Wrapper.class)
.addConstructorArgReference(bddName)
.getBeanDefinition();
((BeanDefinitionRegistry)beanFactory).registerBeanDefinition("wrapperFor" + bddName, wrappedBdd);
}
}
}
}
So I'm getting all the beans one by one in for-each loop, then I'm asking whether the bean has an annotation "wrappable" on it in the if condition. If it has - it must be wrapped.
In this case I create an "artificial" bean definition for Wrapper and add a constuctor that will reference my bean that should be wrapped.
Then I register the bean definition by adding it to the application context.
Run the code above and you'll see the output similar to mine:
class wrappers.WrappersJavaConfig$$EnhancerBySpringCGLIB$$f88f147d ==> wrappers.WrappersJavaConfig$$EnhancerBySpringCGLIB$$f88f147d#1283bb96
class wrappers.Foo ==> wrappers.Foo#74f0ea28
class wrappers.Bar ==> wrappers.Bar#f6efaab
class wrappers.ShouldNotBeWrapped ==> wrappers.ShouldNotBeWrapped#3c19aaa5
class wrappers.WrappersEnrichmentBFPP ==> wrappers.WrappersEnrichmentBFPP#3349e9bb
class wrappers.Wrapper ==> Wrapper forwrappers.Foo#74f0ea28
class wrappers.Wrapper ==> Wrapper forwrappers.Bar#f6efaab
As you see, two last lines are lines that correspond to the wrapper beans created for the same instances of Foo and Bar but nothing was created for the ShouldNotBeWrapped bean
The APIs used are somewhat obscure and look outdated, but again its pretty advanced stuff and works at the level of spring container infra itself. Having said that, there are a lot of tutorials about BeanFactoryPostProcessor-s.
Since Using BFPPs is not a usual task, and although I've provided the solution, I don't see any real usage of it, wrappers can't be used "instead" of Foo or Bar classes, do not have their APIs, etc. Maybe you could explain why do you need wrappers over some beans. Usually people use Aspects/BeanPostProcessors (not BFPP but BPP) to wrap the class into dynamic proxy (cglib / java.lang.Proxy) and add an additional behavior, stuff like #Transactional, cache handling and so forth is implemented in spring with BeanPostProcessors, so consider checking this direction as well.
It is possible and in fact a feature in spring.
Spring can inject your dependency with the correct generic type.The following example is from spring documentation.
Suppose you have an interface
public interface Store<T>{...}
and two beans. One implements Store,one implemenets Store.
#Configuration
public class MyConfiguration {
#Bean
public StringStore stringStore() {
return new StringStore();
}
#Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
You can declare the type with the correct type parameter and spring will inject the right bean for you.
#Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
#Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
I am using an ObjectProvider to create instances of a prototype scope bean using the getObject() method. Something like this
#Configuration
class Config {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
SomeType typeOne() {
return new SomeType();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
SomeType typeTwo(String param) {
return new SomeType(param);
}
}
#Service
class Service {
private ObjectProvider<SomeType> objectProvider;
public Service(
ObjectProvider<SomeType> objectProvider) {
this.objectProvider = objectProvider;
}
#Override
public String performAction() {
return getSomeType().doAction();
}
private SomeType getSomeType() {
return objectProvider.getObject();
}
}
But since there are two beans of the type that the ObjectProvider is trying to get (SomeType), I get a NoUniqueBeanDefinitionException. (And I do need the other bean of the same type, because that one I need to provide parameters using objectProvider.getObject(Object... params) )
Playing around and debugging Spring I saw that if you name your ObjectProvider exactly like your bean then it works, something like:
private ObjectProvider<SomeType> typeOne;
My question is, are there other ways to use an ObjectProvider and manage to resolve ambiguity, or is this approach the way to go?
Short answer is you just need to properly qualify the ObjectProvider you want injected, like this:
public Service(#Qualifier("typeOne") ObjectProvider<SomeType> objectProvider) {
this.objectProvider = objectProvider;
}
With Spring configuration, when you specify a bean via a method, and don't specify it's name with #Bean("NAME"), Spring uses the method name as the bean name.
Similarly, when injecting a bean that is not specified by #Qualifier("NAME"), Spring takes the injected variable as the name, if that don't exists or is not unique, you might get some exceptions informing you about this (like the NoUniqueBeanDefinitionException you facing).
So, if you match the bean name and the injected variable name you don't need to be more specific, but if you don't, #Qualifier is there to your rescue :D
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.
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;
I want expose instances managed by an external framework to CDI applications using #Inject. These instances must be provided this other framework since their lifecycle is based on various caching strategies.
Ex: same instance is visible within same thread scope, might live across many request scopes, session scope is not applicable. Seems I need to define a new scope targeting these kind of instances?
What is the best way to do this? An extension, is it possible with producer methods?
I almost got it to work with producer methods using the following:
#Inject
#CustomInject
FwObject obj;
#Produces
#CustomInject
FwObject createConfig(InjectionPoint p) {
return (FwObject) ctx.get((Class<?>) p.getType());
}
But this force me to be explicit about the type produced which is not possible since there is no common framework interface.
Any help appreciated.
Maybe with producer methods, all depends on what you need, but an extension is probably the best way to go. If you need to go with a new scope (if you're using JSF the Conversation scope may work) you will certainly need to create an extension.
I think I solved it by creating a custom scope. The following article was really helpful:
http://www.verborgh.be/articles/2010/01/06/porting-the-viewscoped-jsf-annotation-to-cdi/
This is a very brief description of how I solved it.
Create custom scope annotation.
import javax.enterprise.context.NormalScope;
#Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#NormalScope
public #interface CustomScope {
}
Create custom context.
import javax.enterprise.context.spi.Context;
public class CustomContext implements Context {
private MyFw myFw = .... ;
#Override
public Class<? extends Annotation> getScope() {
return CustomScope.class;
}
#Override
public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext) {
Bean bean = (Bean) contextual;
return (T) myFw.get(bean.getBeanClass());
}
#Override
public <T> T get(Contextual<T> contextual) {
Bean bean = (Bean) contextual;
return (T) myFw.get(bean.getBeanClass());
}
#Override
public boolean isActive() {
return true;
}
}
Create extension and register context.
import javax.enterprise.inject.spi.Extension;
public class CustomContextExtension implements Extension {
public void afterBeanDiscovery(#Observes AfterBeanDiscovery event, BeanManager manager) {
event.addContext(new CustomContext());
}
}
Register extension.
Add CustomContextExtension to META-INF/javax.enterprise.inject.spi.Extension
Add CustomScope to framework object.
#CustomScope
public class FwObject { ... }
Inject FwObject using #Inject where needed.
public class MyService {
#Inject
FwObject obj;
}