Find annotation in Spring proxy bean - spring

I have created my own annotation for classes: #MyAnnotation, and have annotated two classes with it.
I have also annotated a few methods in these classes with Spring's #Transactional. According to the Spring documentation for Transaction Management, the bean factory actually wraps my class into a proxy.
Last, I use the following code to retrieve the annotated beans.
Method getBeansWithAnnotation correctly returns my declared beans. Good.
The class of the bean is actually a proxy class generated by Spring. Good, this means the #Transactional attribute is found and works.
Method findAnnotation does not find MyAnnotation in the bean. Bad. I wish I could read this annotation from the actual classes or proxies seamlessly.
If a bean is a proxy, how can I find the annotations on the actual class ?
What should I be using instead of AnnotationUtils.findAnnotation() for the desired result ?
Map<String,Object> beans = ctx.getBeansWithAnnotation(MyAnnotation.class);
System.out.println(beans.size());
// prints 2. ok !
for (Object bean: services.values()) {
System.out.println(bean.getClass());
// $Proxy
MyAnnotation annotation = AnnotationUtils.findAnnotation(svc.getClass(), MyAnnotation.class);
//
// Problem ! annotation is null !
//
}

You can find the real class of the proxied bean by calling AopProxyUtils.ultimateTargetClass.
Determine
the ultimate target class of the given bean instance, traversing not
only a top-level proxy but any number of nested proxies as well - as
long as possible without side effects, that is, just for singleton
targets.

The solution is not to work on the bean itself, but to ask the application context instead.
Use method ApplicationContext#findAnnotationOnBean(String,Class).
Map<String,Object> beans = ctx.getBeansWithAnnotation(MyAnnotation.class);
System.out.println(beans.size());
// prints 2. ok !
for (Object bean: services.values()) {
System.out.println(bean.getClass());
// $Proxy
/* MyAnnotation annotation = AnnotationUtils.findAnnotation(svc.getClass(), MyAnnotation.class);
// Problem ! annotation is null !
*/
MyAnnotation annotation = ctx.findAnnotationOnBean(beanName, MyAnnotation.class);
// Yay ! Correct !
}

Related

Spring Boot Bean: value passed in constructor is not null in constructor, but null in CGLIB proxy

I have the following repository implementation in Kotlin:
#Repository
class SapArticleRepository(jooqDsl: DSLContext, jooqConfiguration: DefaultConfiguration) :
AbstractSapRepository<TrmSapArticleRecord, TrmSapArticlePojo>(jooqDsl, jooqConfiguration)
with the following base class:
abstract class AbstractSapRepository<R : UpdatableRecord<R?>, TPojo>(
var dao: DAOImpl<R, TPojo, Long>,
jooqDsl: DSLContext,
jooqConfiguration: DefaultConfiguration,
) {
fun findById(id: Long) = dao.findById(id)
}
When running an Integration test, I get an exception, because dao is null. I checked (by debugging): dao is not null when the constructor is called, but it indeed is when findById is called. I noticed that the object references are not the same (because of CGLIB, the Spring proxy), but I don't know what happens between the constructor call and the time the proxied bean is created.
I tried an abstract function getDaoImpl() an which is implemented in SapArticleRepository (returning the object instance) and the calling that method (instead of accessing dao), but that seems overly complicated. There must be a way to pass the constructor argument/field in a way so it is still present by the time the Bean is used.
Note. TrmSapArticleDao is just a Jooq-generated class.
Edit: I already found Constructor-Injected field is null in Spring CGLIB enhanced bean, but the accepted answer does not seem to answer my question.
As always, I found the solution 5 minutes after posting the question...
The problem was that fields passed in the constructor are final in Kotlin by default. CGLIB can't intercept final fields/method and therefore leaves them initialized with null (see this answer on GitHub.
The solution is to make the constructor field open. So above code works when defining the constructor of the abstract class as follows:
abstract class AbstractSapRepository<R : UpdatableRecord<R?>, TPojo>(
open var dao: DAOImpl<R, TPojo, Long>,
jooqDsl: DSLContext,
jooqConfiguration: DefaultConfiguration,
)

Multiple Constructor injection using Java Annotations

The SPRING doc says the following
Spring Framework 4.3, an #Autowired annotation on such a constructor
is no longer necessary if the target bean only defines one constructor
to begin with. However, if several constructors are available, at
least one must be annotated to teach the container which one to use.
As i understand if there are multiple constructors and we have not annotated any of them then i will get an error . I ran the following code
#Component // this is bean id
public class TennisCoach implements Coach {
private FortuneService fortuneservice;
public TennisCoach(FortuneService thefortuneservice) {
System.out.println(" inside 1 arg constructter");
fortuneservice = thefortuneservice;
}
public TennisCoach() {
System.out.println(" inside 0 arg constructter");
}
I call that using the below code
TennisCoach theCoach = myapp.getBean("tennisCoach", TennisCoach.class);
But i didn't get the error .I got the O/P as
inside 0 arg constructter
Why?
It looks like the text you've quoted from the Spring docs doesn't apply to a case where one of the constructors is a no-args (default) constructor. You can see this very easily if you try and add an bean reference parameter to it.
Spring attempts to determine the candidate constructors using AutowiredAnnotationBeanPostProcessor and in your scenario, will not find any single or autowired constructor, so it will record the no-args one and instantiate it in SimpleInstantiationStrategy.

Spring #Transactional value param with SpEL (Spring expression language)

In one of my service classes I have some methods annotated as such :
#Transactional(value="foodb")
public Bar getMeSomething(){
}
I recently learned about #Value with the power of Spring EL to get some values stored in a properties file.
such as
#Value("${my.db.name}")
which works like a charm.
Now I'm trying to do the same with
#Transactional(value="${my.db.name}")
with no success ...
I get the following exception :
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named '${my.db.name}' is defined: No matching PlatformTransactionManager bean found for qualifier '${my.db.name}' - neither qualifier match nor bean name match!
Is what I am trying to do even supported by Spring ?
What can I do to get the my.db.name value inside that #Transactional annotation
Thanks
Nope, it's not supported.
Here's an excerpt from org.springframework.transaction.annotation.SpringTransactionAnnotationParser
public TransactionAttribute parseTransactionAnnotation(Transactional ann) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
rbta.setPropagationBehavior(ann.propagation().value());
rbta.setIsolationLevel(ann.isolation().value());
rbta.setTimeout(ann.timeout());
rbta.setReadOnly(ann.readOnly());
rbta.setQualifier(ann.value()); // <<--- this is where the magic would be
// if it was there, but it isn't

Spring-Wicket: Bean Injection with Resource

I am writing tests for my wicket-application and need to inject a Spring Bean into a page (done by annotation) to do this.
Consider following code:
protected void setUp() {
tester = new WicketTester();
scanService = new ScanService();
ApplicationContextMock appctx=new ApplicationContextMock();
appctx.putBean("pxGenericService", new PxGenericServiceImpl());
tester.getApplication().getComponentInstantiationListeners().add(new SpringComponentInjector(tester.getApplication(), appctx));
}
This actually seem to work (no nullpointer). The problem is: the bean got a resource (variable with #Resource annotation) and when I run the test on the page, this resource turns out to be null (nullpointer exception). How do I fix this problem?
You also have to add an instance of all dependencies your bean has to the mock application context. So add an instance of the class PxGenericServiceImpl uses to appctx.
I don't think that SpringComponentInjector supports #Resource. The only supported annotations are #SpringBean and #Inject. See AnnotProxyFieldValueFactory:
#Override
public boolean supportsField(final Field field)
{
return field.isAnnotationPresent(SpringBean.class) || field.isAnnotationPresent(Inject.class);
}

How to inject a value to bean constructor using annotations

My spring bean have a constructor with an unique mandatory argument, and I managed to initialize it with the xml configuration :
<bean name="interfaceParameters#ota" class="com.company.core.DefaultInterfaceParameters">
<constructor-arg>
<value>OTA</value>
</constructor-arg>
</bean>
Then I use this bean like this and it works well.
#Resource(name = "interfaceParameters#ota")
private InterfaceParameters interfaceParameters;
But I would like to specify the contructor arg value with the annocations, something like
#Resource(name = "interfaceParameters#ota")
#contructorArg("ota") // I know it doesn't exists!
private InterfaceParameters interfaceParameters;
Is this possible ?
Thanks in advance
First, you have to specify the constructor arg in your bean definition, and not in your injection points. Then, you can utilize spring's #Value annotation (spring 3.0)
#Component
public class DefaultInterfaceParameters {
#Inject
public DefaultInterfaceParameters(#Value("${some.property}") String value) {
// assign to a field.
}
}
This is also encouraged as Spring advises constructor injection over field injection.
As far as I see the problem, this might not suit you, since you appear to define multiple beans of the same class, named differently. For that you cannot use annotations, you have to define these in XML.
However I do not think it is such a good idea to have these different beans. You'd better use only the string values. But I cannot give more information, because I dont know your exact classes.
As Bozho said, instead of constructor arg you could set the property...#PostConstruct will only get called after all the properties are set...so, you will still have your string available ...

Resources