Defining bean in a spring-boot application (v 2.3.12) as below:
#MyConditionalProperty(key="name", value = "false")
#Bean
#Profile({"local", "dev"})
public FilterRegistrationBean<Filter> myfilterBean(FilterOne filter){
return registerFilter(filter, Integer.MIN_VALUE);
}
#MyConditionalProperty(key="name", value = "true")
#Bean
public FilterRegistrationBean<Filter> myfilterBean(FilterTwo filter){
return registerFilter(filter, Integer.MIN_VALUE);
}
private FilterRegistrationBean<Filter> registerFilter(Filter filter, int order) {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setOrder(order);
filterRegistrationBean.addUrlPatterns("/v1/*");
return filterRegistrationBean;
}
On local active profile, when the value of the "name" property is "false", myfilterBean is created as expected with dependency on FilterOne. Although, across server restarts, don't see it being created consistently.
What could be the reason for this inconsistency and random behavior? Can it be related to the same method name myfilterBean being used to create the bean with one or the other dependency?
Renaming one of the myfilterBean method works. Renamed it as myFilterBeanOne and appropriate bean is loaded based on the conditional property value.
Related
I need to create a Conditional Bean in Spring. The use case is as following:
Class 1
In this class we are trying to create the Bean, which should be created for some clients who have the required permission, and for others it will return empty(). Thus the application should boot-up for all the clients without the BeanCreationException
#org.springframework.context.annotation.Configuration
public class SomeBeanConfiguration {
#Bean
public Optional<SomeBean> someBean() {
// whoAmI() ? returns IAmClient_1 - for whom this bean should be created
// whoAmI() ? returns IAmClient_2 - for whom this bean should not be created
final String somePermission = whoAmI();
try {
return Optional.of(SomeBean.builder()
.withPermission(new SomeCredentialsProvider(somePermission))
.build());
} catch (Exception ex) {
LOG.error("SomeBean creation exception : ", ex);
}
return Optional.empty();
}
}
Class 2
Where we are using this Bean in Constructor injection
#Bean
public SomeHelper someHelper(Optional<SomeBean> someBean) {
return new someHelper(someBean);
}
But the someHelper for client, who have permission are also getting an Optional.empty() in constructor.
What I am doing wrong here? Can anyone please help?
You need to change your method that's creating the bean. It should not be returning a bean of type Optional, it should be returning a bean of type SomeBean. Also, consider rewriting your logic to something more understandable, like dropping the catch block and creating the bean based on the output of whoAmI().
#Bean
public SomeBean someBean() {
// whoAmI() ? returns IAmClient_1 - for whom this bean should be created
// whoAmI() ? returns IAmClient_2 - for whom this bean should not be created
String somePermission = whoAmI();
if (somePermission.equals("IAmClient_1") {
return SomeBean.builder().withPermission(newSomeCredentialsProvider(somePermission)).build());
} else {
return null;
}
}
Now, when you autowire the Optional, the optional will contain the bean for IAmClient_1, and will be empty for all other cases.
In my opinion, it would be better to always construct SomeBean and just modify its behavior based on the value of the permission you're checking, but that's up to you.
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){
...
}
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
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;
}
}
}
I have this bean in my Spring Java config:
#Bean
#Scope( proxyMode=ScopedProxyMode.TARGET_CLASS, value=SpringScopes.DESKTOP )
public BirtSession birtSession() {
return new BirtSession();
}
For tests, I need a mock without a scope (there is no "Desktop" scope in the test). But when I create a configuration for my test which imports the above configuration and contains:
#Bean
public BirtSession birtSession() {
return new MockSession();
}
I get a "Desktop" scoped mocked bean :-(
How do I make Spring "forget" the #Scope annotation?
PS: It works when I don't use #Import and use copy&paste but I don't want to do that.
The problem seems to be in ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod() that uses ScopedProxyCreator.createScopedProxy() static method to create the scoped bean definition:
// replace the original bean definition with the target one, if necessary
BeanDefinition beanDefToRegister = beanDef;
if (proxyMode != ScopedProxyMode.NO) {
BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS);
beanDefToRegister = proxyDef.getBeanDefinition();
}
As the BeanDefinitionHolder returns a RootBeanDefinition instead of ConfiguratioClassBeanDenition the scoped proxy bean definition (ie, the ScopedProxyFactoryBean) cannot be overriden by another Java Configuration class.
A workaround could be declaring the scoped beans to override in a xml configuration file and importing it with #ImportResource.
The problem isn't Spring keeping the annotation, the problem is that Spring first tries to parse the "productive" config and in order to do that, it checks whether the scope is available. Spring checks scopes eagerly. So it never gets to the second/overriding bean definition.
Create a dummy scope:
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
public class MockSpringScope implements org.springframework.beans.factory.config.Scope {
private Map<String, Object> objects = new HashMap<String, Object>();
#Override
public Object get( String name, ObjectFactory<?> objectFactory ) {
Object result = objects.get( name );
if( null == result ) {
result = objectFactory.getObject();
objects.put( name, result );
}
return result;
}
#Override
public Object remove( String name ) {
return objects.remove( name );
}
#Override
public void registerDestructionCallback( String name, Runnable callback ) {
// NOP
}
#Override
public Object resolveContextualObject( String key ) {
// NOP
return null;
}
#Override
public String getConversationId() {
// NOP
return null;
}
}
and register that under as "Desktop" scope. That will Spring allow to successfully parse the production config.