How to refer String Bean in #ConditionalOnProperty - java

I need to initialize a Configuration based on some other bean value that I am fetching from database,
how can we refer the bean in #ConditionalOnProperty value attribute ?
Or is there any other way to achieve the same.
// Bean that will query database
#Bean
public String checkAndCreate()
{
// Logic to Query DB
return "true";
}
Want to Refer the bean checkAndCreate in value
#ConditionalOnPropery(value = "", havingValue = "true")
#Configuration
public class MyConfiguration
{
// Some configuration Code
}

Don't use #ConditionalOnProperty, use #Conditional with a custom condition instead. The ConditionContext should give you access to this bean. You may need to use #DependsOn on to ensure that the class providing the string bean is already initialized.

Related

How to add #Qualifier

How can I add a qualifier to distinguish between these two beans? I know I need to use the #Qualifier annotation but I am not sure how to add it in the beans and then how to create the autowired object with reference to the appropriate bean.
#Configuration
#Slf4j
#PropertySources(PropertySource("classpath:application.properties"),
PropertySource(value = ["file:\${credentials.config}"]))
class CredentialsConfig(#Autowired private val env: Environment) {
#Bean fun getCredentials(): Credentials? {
val user: String = env.getRequiredProperty("user1")
val pass: String = env.getRequiredProperty("pass1")
return Credentials.info(user, pass)
}
#Bean fun getCredentials2(): Credentials {
val user: String = env.getRequiredProperty("user2")
val pass: String = env.getRequiredProperty("pass2")
return Credentials.info(user, pass)
}
}
In situations like this, I find it beneficial to explicitly name my beans so it is more clear which one I am picking. Otherwise, you will end up with what Spring decides to call it (based on the method name). When we want to inject a bean, but there are more than one of them, we use the #Qualifer annotation at the injection point, specifying the name of the bean we care about.
So...
// In CredentialsConfig
#Bean("firstCredentials) fun firstCredentials(): Credentials = TODO()
#Bean("secondCredentials) fun secondCredentials(): Credentials = TODO()
And when wiring in one of these, you can add a #Qualifier to pick your specific implementation (note, if you use constructor injection, you don't need #Autowired):
#Component
class MyComponent(#Qualifier("firstCredentials") creds: Credentials) { ... }
You could just add #Qualifier with bean name whenever you do an Autowire of Credentials.
#Autowired
#Qualifier("getCredentials")
Credentials credentials;

How to use Spring ObjectProvider with more than one bean definition

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

Register different configurations of the same bean

Using Spring 5 on Java 9....
Not even sure this is possible. Have a very simple class:
public class Exchange {
#Autowired
private OtherService other;
#JmsListener(destination = {some.queue})
#Transactional
public void receive(String payload) {
other.send(payload)
}
}
Both Exchange and OtherService just needs a couple of configuration properties (i.e. some.queue) to work. Would like to register (either through BeanDefinitionRegistryPostProcessor or ApplicationContextInitializer) multiple instances of the Exchange bean but with prefixed configuration properties. Anyway to alter attribute definition when registering a bean?
I think you want a combination of two things, #ConfigurationProperties and #Qualifier.
ConfigurationProperties let's you supply a prefix that applies to all of the #Value properties loaded injected in tho that class.
#Qualifier allows you to specify which one of potentially many valid #Autowire targets you'd like. Specify the bean name and you are set.
Quick untested example:
#Component("FooExchange")
#ConfigurationProperties(prefix = "foo")
class FooExchange implements Exchange {
#Value("enabled") private boolean enabled;
...
}
#Component("BarExchange")
#ConfigurationProperties(prefix = "bar")
class BarExchange implements Exchange {
#Value("enabled") private boolean enabled;
...
}
And the properties you'd define are:
foo.enabled = true
bar.enabled = true
And when you inject one:
#Autowired
#Qualifier("FooExchange")
private Exchange myInjectedExchange; // Which will get FooExchange
Edit: You may beed to annotate one of your configuration classes or your main class with #EnableConfigurationProperties to enable the configuration properties.

#Conditional on bean initialization

I have been searching for this for a while.
Till now, I found this blog very useful but did not solve my problem.
I want to #Autowired a bean only if a flag is true, else I want that to be null
Use Case:
If one of the DB is under maintenance, I do not want my app to fail.
#Bean("sqlDatabase")
public Database getSqlDatabase(#Value("${datasource.sql.url}") String url,
#Value("${datasource.sql.username}") String username, #Value("${datasource.sql.password}") String password,
#Value("${datasource.poolsize.min}") int minPoolSize, #Value("${datasource.poolsize.max}") int maxPoolSize,
#Value("${database.enable-sql}") boolean isSqlEnabled) {
if (isSqlEnabled)
return Database.builder().url(url).pool(minPoolSize, maxPoolSize).username(username).password(password)
.build();
else
return null;
}
Now, in this case, its throwing error as I cannot autowire a null bean.
I wanted to use #Conditional but my case is a bit complex. I already need all 3 databases to be updated. I just want to skip one of them if conditions are not met.
You can use profiles.
One profile for every database
db1
db2
db3
Than annotate the bean class or the bean method with the profile that must be activated to use that bean like
#Profile("db1")
#Bean("db1")
public Database getSqlDatabase(...){...}
When you start your app, beans annotated with #Profile will only be created, if the regarding profile is activated.
You activate a profile by setting the property 'spring.profiles.active'.
To activate db1 and db2 :
spring.profiles.active=db1,db3
You can set that property in a properties file or as a command line parameter.
Profiles give you a lot of flexibility to change you spring context by configuration
you can annotate many beans with the same profile
you can annotate a configuration class with a profile
you can use profile specific property files
you can use many profiles in one #Profile annotations. Logical 'or' will be used, so a bean annotated with #Profile("db1","db2") will be created if profile 'db1' is active or profile 'db2' is active
if you want something else than 'or' you can use #Conditional to define your own logic
Please note : If you use do not use component scan or xml configuration, the annotation #Profile at a bean class has no effect. You need to annotate the bean method with #Profile or the whole configuration class instead.
We can leverage #Conditional property during the initial component scan to avoid error while initializing based on the environment properties.
A valid use case would be enable a repository to be scanned for bean initialization when the environment db.enabled property is true
example:
https://javapapers.com/spring/spring-conditional-annotation/
Conditional helper class
public class DocumentDBInitializerPresence implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
DocumentDBInitializerInfo employeeBeanConfig = null;
try {
employeeBeanConfig = (DocumentDBInitializerInfo)context.getBeanFactory().getBean("DocumentDBInitializerInfo");
} catch(NoSuchBeanDefinitionException ex) {
System.out.println("BEAN NOT FOUND:: " + employeeBeanConfig != null );
}
System.out.println("BEAN FOUND :: " + employeeBeanConfig != null );
boolean matches = Boolean.valueOf(context.getEnvironment().getProperty("db.enabled"));
System.out.println("CONFIG ENABLED :: " + employeeBeanConfig != null );
return employeeBeanConfig != null && matches;
}
}
Using in service
#Service
#RefreshScope
#Conditional(value= DocumentDBInitializerPresence.class)
public class RepositoryDocumentDB {
private static Logger LOGGER = LoggerFactory.getLogger(RepositoryDocumentDB.class);
public static final String DOCUMENT_DB_LOCAL_HOST = "localhost";
DocumentDBInitializerInfo documentDbConfig;
#Autowired
public RepositoryDocumentDB(final DocumentDBInitializerInfo documentDbConfig) {
this.documentDbConfig = documentDbConfig;
}
}
This will not throw error on application startup if you are not autowiring
RepositoryDocumentDB anywhere yet and the db.enabled is set to false.
Hope this helps!

How to Dynamically create Spring bean and Set Annotations on the newly created bean

Am trying to to dynamically create Spring bean and Set Annotations on the newly created bean to unit test the below piece of code
class BeanMetadata
{
int id;
String type;
String beanName;
Date createdAt;
Date createdBy;
}
This method gets the bean name from BeanMetadata and searches in the applicationCOntext for the given bean and checks if the bean has #OperationExecutePermission or #AdministerPermission annotations present on the bean.
So am trying to to dynamically create Spring bean and Set these annotations on the newly created
void addCommandPermissions(BeanMetadata command) {
if (applicationContext.containsBean(command.getBeanName())) {
Object bean = applicationContext.getBean(command.getBeanName());
Class<?> beanClass = bean.getClass();
if (beanClass.isAnnotationPresent(AdministerPermission.class)) {
overrideAdminPermission = beanClass.getAnnotation(AdministerPermission.class).name();
}
if (beanClass.isAnnotationPresent(OperationExecutePermission.class)) {
overrideExecPermission = beanClass.getAnnotation(OperationExecutePermission.class).name();
}
}
I am trying to achieve if (beanClass.isAnnotationPresent(AdministerPermission.class)) must be true or if (beanClass.isAnnotationPresent(OperationExecutePermission.class)) must be true for the newly created bean.
You cannot add annotations to anything at runtime.
The best you can do here is create a class that has the annotations. You can then test your code with classes that have the annotations and classes that don't.

Categories

Resources