Reference to a Spring GenericBeanDefinition - spring

I am trying to define my beans programmatically. I have 1 DAO bean, used by 2 other services beans. The DAO bean is injected as a constructor argument.
How can I get a reference to the DAO bean to build the service beans (analog to <bean ref="myDAO"/>) ?
My DAO is defined as (with DefaultListableBeanFactory beanFactory):
final GenericBeanDefinition myDAODefinition = new GenericBeanDefinition();
myDAODefinition.setBeanClassName("com.xxx.dao");
final BeanDefinitionHolder myDAOHolder = new BeanDefinitionHolder(myDAODefinition,"myDAO");
BeanDefinitionReaderUtils.registerBeanDefinition(myDAOHolder, beanFactory);
Then my services beans:
final GenericBeanDefinition srv1Definition = new GenericBeanDefinition();
srv1Definition.setBeanClassName("com.xxx.service1");
srv1Definition.setConstructorArgumentValues(new ConstructorArgumentValues() {
{
addGenericArgumentValue(*** ref to "myDAO" holder ***);
}
});
final BeanDefinitionHolder srv1Holder = new BeanDefinitionHolder(srv2Definition, "srv1");
and:
final GenericBeanDefinition srv2Definition = new GenericBeanDefinition();
srv2Definition.setBeanClassName("com.xxx.service2");
srv2Definition.setConstructorArgumentValues(new ConstructorArgumentValues() {
{
addGenericArgumentValue(*** ref to "myDAO" holder ***);
}
});
final BeanDefinitionHolder srv2Holder = new BeanDefinitionHolder(srv2Definition, "srv2");
How to reference "myDAO" Holder to inject it twice in other definitions ?
Note that I cannot use annotations.
Thanks in advance.

Found out, just use:
Object myDAORef = new RuntimeBeanReference("myDAO");
and inject it as the constructor argument.

Related

how to inject an array of Beans in spring boot?

In Spring, we can inject a bean like bellowing code:
#Bean
public AspectJExpressionPointcutAdvisor configurabledvisor() {
System.out.println("configurabledvisor");
AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
advisor.setExpression(pointcut);
advisor.setAdvice(new LogAroundAdvice());
return advisor;
}
In some situation, it is needed that an array of beans should be injected, the pseudo code like that:
#Bean[]
public AspectJExpressionPointcutAdvisor[] configurabledvisorArray() {
System.out.println("configurabledvisor");
AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
advisor.setExpression(pointcut);
advisor.setAdvice(new LogAroundAdvice());
AspectJExpressionPointcutAdvisor advisor1 = new AspectJExpressionPointcutAdvisor();
advisor.setExpression(pointcut2);
advisor.setAdvice(new AnotherAdvice());
return new AspectJExpressionPointcutAdvisor[]{advisor, advisor1};
}
Any help would be appreciated, thanks in advance.
Demo code address is: https://github.com/sluk3r/inject-multipleBeans/tree/main
You just need to remove the []
#Bean
public AspectJExpressionPointcutAdvisor[] configurabledvisorArray() {
...
}
then you can inject (using autowire, constructor, setter, ...) your bean everywhere
#Autowire
private AspectJExpressionPointcutAdvisor[] configurabledvisorArray;
or as a dependency of another bean
#Bean
String testBean(AspectJExpressionPointcutAdvisor[] configurabledvisorArray){
...
}

Getting Class annotation for a given Spring Bean

I have two custom annotation as described below.
CustomAnnotationMain is a Spring Component based annotation.
CustomAnnotationChild is a Spring Bean based annotation.
Below is the code snippet which uses the 2 custom annotations.
#CustomAnnotationMain(value = "parent")
public class MainClass{
#CustomAnnotationChild(value = "child1")
public ObjectBuilder getObject1() {
// logic
}
#CustomAnnotationChild(value = "child2")
public ObjectBuilder getObject2() {
// logic
}
}
Question: How can I get the list of all CustomAnnotationMain annotated classes and also all the beans + annotation infos that are available as part of the component?
I did the following to get all the beans annotated with #CustomAnnotationChild. But I am not sure how to access the class in which the bean is available. I need to access #CustomAnnotationMain for a given bean.
allBuilders = context.getBeansOfType(ObjectBuilder.class);
PS: This is not Spring Boot based project. I use only the spring core libs.
I did something similar. Introduced an interface Proxyable and need to find all the beans annotated with the interface or create proxy s for all defined interfaces.
https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/service/ProxyableScanRegistrar.java
In your case you should replace Proxyable with your CustomAnnotationMain.
The logic of ClassPathScanningCandidateComponentProvider definition can be changed to reflect your filter (I need there interfaces only).
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LOG.debug("Registering #Proxyable beans");
// 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()};
}
// using these packages, scan for interface annotated with Proxyable
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment) {
// Override isCandidateComponent to only scan for interface
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isIndependent() && metadata.isInterface();
}
};
provider.addIncludeFilter(new AnnotationTypeFilter(Proxyable.class));
ControllerFactory factory = getControllerFactory((DefaultListableBeanFactory) registry);
// Scan all packages
for (String basePackage : basePackages) {
for (BeanDefinition beanDefinition : provider.findCandidateComponents(basePackage)) {
try {
Class c = this.getClass().getClassLoader().loadClass(beanDefinition.getBeanClassName());
if (!hasImplementingClass(c, basePackages)) {
//creating missing beans logic is skipped
}
} catch (ClassNotFoundException e) {
throw new SOAControllerCreationException("cannot create proxy for " + beanDefinition.getBeanClassName());
}
}
}
}
}
Hope it helps

Can I programmatically add a qualifier to a bean?

I am registering transaction managers in my code, I would normally use annotation based configuration but as I don't know until runtime how many data sources (and hence transaction managers) there will be, I have to programmatically register these, as follows:
private final void registerTransactionManagerBean(final DataSource dataSource, ConfigurableApplicationContext context) {
String transactionManagerName = this.getName() + "-transactionManager";
context.getBeanFactory().registerSingleton(transactionManagerName, new DataSourceTransactionManager(dataSource));
LOG.info("Registering transaction manager under name : " + transactionManagerName);
}
Assuming this.getName() returned 'mydb', I originally expected to be able to qualify a transaction manager like this:
#Transactional("mydb-transactionManager")
What I've realised however is the value of that annotation refers to the qualifier and not the name. I did a quick test by declaring a bean as below and it works:
#Bean
#Qualifier("mydb-transactionManager")
public PlatformTransactionManager test() {
return new DataSourceTransactionManager(new EmbeddedDatabaseBuilder().build());
}
My question is, is there a way I can programmatically add a qualifier when registering a bean?
UPDATE
I've worked this out, I'm falling foul of this problem (in BeanFactoryAnnotationUtils:isQualifierMatch):
catch (NoSuchBeanDefinitionException ex) {
// ignore - can't compare qualifiers for a manually registered singleton object
}
I am manually registering my transaction manager bean so I presume this is why I'm stuck. I'm not really sure what options that gives me apart from to not programmatically register transaction managers as a runtime thing sadly.
I've worked this out, I'm falling foul of this problem:
catch (NoSuchBeanDefinitionException ex) {
// ignore - can't compare qualifiers for a manually registered singleton object
}
I am manually registering my transaction manager bean so I presume this is why I'm stuck. I'm not really sure what options that gives me apart from to not programatically register transaction managers as a runtime thing sadly.
Raised as a JIRA issue - https://jira.spring.io/browse/SPR-11915
public class RuntimeRegistrationWithQualifierTest {
private AnnotationConfigApplicationContext context;
#Test
public void beanWithQualifier() {
final GenericBeanDefinition helloBeanDefinition = new GenericBeanDefinition();
helloBeanDefinition.addQualifier(new AutowireCandidateQualifier(Hello.class));
final GenericBeanDefinition worldBeanDefinition = new GenericBeanDefinition();
worldBeanDefinition.addQualifier(new AutowireCandidateQualifier(World.class));
final DefaultListableBeanFactory factory = context.getDefaultListableBeanFactory();
factory.registerBeanDefinition("helloBean", helloBeanDefinition);
factory.registerSingleton("helloBean", "hello");
factory.registerBeanDefinition("worldBean", worldBeanDefinition);
factory.registerSingleton("worldBean", "world");
context.register(Foo.class);
context.refresh();
final Foo foo = context.getBean(Foo.class);
assertThat(foo.hello).isEqualTo("hello");
assertThat(foo.world).isEqualTo("world");
}
#Before
public void newContext() {
context = new AnnotationConfigApplicationContext();
}
#Qualifier
#Retention(RUNTIME)
#Target({FIELD, PARAMETER})
#interface Hello {}
#Qualifier
#Retention(RUNTIME)
#Target({FIELD, PARAMETER})
#interface World {}
static class Foo {
final String hello;
final String world;
Foo(#Hello final String hello, #World final String world) {
this.hello = hello;
this.world = world;
}
}
}

Spring get bean only if already instanced for scope

Hi i'm using Spring 3
i'm using
applicationContext.getBeansOfType
is there a possibility to get only the beans already instanciated for the current scope?
I don't want to instanciate Beans which have not already been used in the current request.
I think that not, but you could write one, something like:
public static List<Object> getBeansInScope(Class<?> type, AbstractApplicationContext ctx, int scope) {
List<Object> beans = new ArrayList<Object>();
String[] names = ctx.getBeanNamesForType(type);
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
for (String name : names) {
Object bean = attributes.getAttribute(name, scope);
if (bean != null)
beans.add(bean);
}
return beans;
}
That only work for request and session scopes.

How to use Spring Transaction when using GenericApplicationContext

When using Distributed Mysql Database, I've Created multiple dataSources, sessionFactory using BeanDefinitionBuilder
But transactional annotation doesn't seem to work when I execute Insert SQL
using getBean('bean name') method below
( (SqlSessionTemplate)context.getBean('bean name') ).insert("xxxx",params)
Would you explain what I've missed?
Private GenericApplicationContext context = new GenericApplicationContext();
BeanDefinitionBuilder sessionFactoryBuilder = BeanDefinitionBuilder.rootBeanDefinition(org.mybatis.spring.SqlSessionFactoryBean.class);
sessionFactoryBuilder.addPropertyReference("dataSource", "dataSource" + beanName);
sessionFactoryBuilder.addPropertyValue("configLocation", "/sqlmap.xml");
context.registerBeanDefinition("sqlSessionFactory" + beanName, sessionFactoryBuilder.getBeanDefinition());
BeanDefinitionBuilder transactionManagerBuilder = BeanDefinitionBuilder.rootBeanDefinition(org.springframework.jdbc.datasource.DataSourceTransactionManager.class);
transactionManagerBuilder.addPropertyReference("dataSource", "dataSource" + beanName);
context.registerBeanDefinition("transactionManager",transactionManagerBuilder.getBeanDefinition());
ctx.refresh();
Please try to add the following codes below and see if it works.
ConfigurableListableBeanFactory factory = context.getBeanFactory();
AspectJAwareAdvisorAutoProxyCreator aspectJPostProcessor = new AspectJAwareAdvisorAutoProxyCreator();
aspectJPostProcessor.setBeanFactory( factory );
aspectJPostProcessor.setProxyClassLoader( context.getClassLoader() );
factory.addBeanPostProcessor(aspectJPostProcessor);

Resources