Instantiate a spring bean conditionally based on a property placeholder - java

Is it possible to configure spring to instantiate a bean or not, depending on a boolean placeholder property? Or at least to exclude a package from annotation scanning based on such a property?

I think you should be using Spring profiles to configure different behaviours. However if you are using annotations you could create an #Configuration object and and a factory method to create a bean based on the property value
e.g.
#Configuration
class ExampleConfig {
private final String prop;
public ExampleConfig(#Value("${your.prop.name}" prop} {
this.prop = prop;
}
#Bean
public YourBeanClass create() {
if (prop.equals("someValue") {
return new YourBeanClass();
}
return new OtherClass(); // must be subclass/implementation of YBC
}
}

You can use ConditionalOnProperty:
#Bean
#ConditionalOnProperty(value = "property", havingValue = "value", matchIfMissing = true)
public MyBean myBean() ...
Also, check this answer.

This may not fit your needs, and I'm assuming that you have control over the class in question (i.e. not vendor code), but have you considered marking the the bean to be lazy loaded? At least, that way it won't get instantiated until it actually gets used, which may happen conditionally depending on the rest of your configuration.

You can also use #Conditional
Step 1 : Create a class that implements Condition
public class ProdDataSourceCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String dbname = context.getEnvironment().getProperty("database.name");
return dbname.equalsIgnoreCase("prod");
}}
Step 2 : Use the above class with #Conditional
#Configuration
public class EmployeeDataSourceConfig {
....
#Bean(name="dataSource")
#Conditional(ProdDataSourceCondition.class)
public DataSource getProdDataSource() {
return new ProductionDatabaseUtil();
}
}
http://javapapers.com/spring/spring-conditional-annotation/

We can use ConditionalOnProperty . Just define a property deployment.environemnt in application.properties file . And based on this property you can control the creation of objects.
#Bean(name = "prodDatasource")
#ConditionalOnProperty(prefix = "deployment" name = "environment"havingValue = "production")
public DataSource getProdDataSource() {
return new ProductionDatasource();
}
#Bean(name = "devDatasource")
#ConditionalOnProperty(prefix = "deployment" name = "environment"havingValue = "dev")
public DataSource getDevDataSource() {
return new devDatasource();
}

Related

Spring Boot: substitute #Configuration class with another

I have a custom configuration class that I am loading using spring factories during bootstrap. The problem is that it is being overwritten by another similar configuration class coming from a spring ** starter package. I've tried excluding the second one, but it still loads. Also tried to set priorities, but that didn't work too.
Here's a snippet of my custom configuration class:
#Slf4j
#Configuration
#RequiredArgsConstructor
public class CustomAwsParamStorePropertySourceLocatorConfig implements PropertySourceLocator
...
And the one I'm trying to exclude that is coming from spring boot aws starter:
public class AwsParamStorePropertySourceLocator implements PropertySourceLocator {
The AwsParamStoreBootstrapConfiguration class has the ConditionalOnProperty annotation at the class level...
#Configuration(proxyBeanMethods = false)
#EnableConfigurationProperties(AwsParamStoreProperties.class)
#ConditionalOnClass({ AWSSimpleSystemsManagement.class, AwsParamStorePropertySourceLocator.class })
#ConditionalOnProperty(prefix = AwsParamStoreProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true)
public class AwsParamStoreBootstrapConfiguration {
private final Environment environment;
public AwsParamStoreBootstrapConfiguration(Environment environment) {
this.environment = environment;
}
#Bean
AwsParamStorePropertySourceLocator awsParamStorePropertySourceLocator(AWSSimpleSystemsManagement ssmClient,
AwsParamStoreProperties properties) {
if (StringUtils.isNullOrEmpty(properties.getName())) {
properties.setName(this.environment.getProperty("spring.application.name"));
}
return new AwsParamStorePropertySourceLocator(ssmClient, properties);
}
So if you configured the property aws.paramstore.enabled=false it should stop that configuration from creating the AwsParamStorePropertySourceLocator bean.
It's important to note, that would also stop the creation of the AWSSimpleSystemsManagement bean which is also created in the AwsParamStoreBootstrapConfiguration class, so if you require that bean, you may need to also create it in your custom Configuration class.
#Bean
#ConditionalOnMissingBean
AWSSimpleSystemsManagement ssmClient(AwsParamStoreProperties properties) {
return createSimpleSystemManagementClient(properties);
}
public static AWSSimpleSystemsManagement createSimpleSystemManagementClient(AwsParamStoreProperties properties) {
AWSSimpleSystemsManagementClientBuilder builder = AWSSimpleSystemsManagementClientBuilder.standard()
.withClientConfiguration(SpringCloudClientConfiguration.getClientConfiguration());
if (!StringUtils.isNullOrEmpty(properties.getRegion())) {
builder.withRegion(properties.getRegion());
}
if (properties.getEndpoint() != null) {
AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
properties.getEndpoint().toString(), null);
builder.withEndpointConfiguration(endpointConfiguration);
}
return builder.build();
}

Overriding spring beans with the same Qualifier Name

I have 2 configuration classes in my spring application.
Configuration and AnotherConfiguration. The AnotherConfiguration is conditioned to create beans only if a certain parameter is provided (this is handled by the ConditionalOnClass annotation).
Configuration.java
#Configuration
public class Configuration {
#Bean
public Stage testStage() {
return someStage1;
}
#Bean
public Stage testStage2() {
return someStage2;
}
}
AnotherConfiguration.java
#Configuration
#ConditionalOnClass()
public class AnotherConfiguration {
#Bean
public Stage testStage2() {
return newStage2;
}
}
The use case is that if I supply an argument that satisfies the Conditional argument for AnotherConfiguration, newStage2 should be returned to all the classes expecting a testStage2 bean. But currently, the testStage2 bean is being resolved from Configuration class instead of being overridden by AnotherConfiguration.
I have tried adding the #Primary annotation to the definition in AnotherConfiguration but that just resolves newStage2 to all the classes expecting bean of type Stage irrespective of the qualifier. Is there a way to instruct spring to override bean definitions only of the same QualifierName (here testStage2.
Due to the project constraints, I cannot make changes to Configuration.java but can make any change to AnotherConfiguration.java keeping the name (testStage2()) same.
I really don't recomend it but
use a conditional instead of an onClass because that will always be true without params
public class Cond implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
and then define the overridden bean to load into the context
#Component("testStage2")
#Conditional(value = Cond.class)
#Primary
public class AnotherStage extends Stage {
public AnotherStage(){
//do whatever
}
}
Sorry bean style
#Configuration
public class AnotherConfiguration {
#Bean("testBean2")
#Conditional(value = Cond.class)
#Primary
public Stage testStage2() {
return newStage2;
}
}

Cant load property in java match-

Trying to implement conditional bean loading in spring. This is the code, problem is, I am not able to load the property inside match method,
#Configuration
public class Class implements Condition {
#Value("${test.property}")
private boolean testProperty;
#Override
public boolean matches(ConditionContext conditionContext,
AnnotatedTypeMetadata annotatedTypeMetadata) {
sout(testProperty);
return true;
}
}
I can however print the property if I inject it into a constructor,
but that does not solve my issue, any thoughts?
For conditional instantiation based on environment variables you could use the #ConditionalOnProperty annotation on top of your bean definitions.
#Configuration
public class Config {
#Bean
#ConditionalOnProperty(name = "your.property", havingValue = "true")
public YourBean instantiateIfTrue() {
// instantiate and return YourBean in case the property is true
}
#Bean
#ConditionalOnProperty(name = "your.property", havingValue = "false")
public YourBean instantiateIfFalse() {
// instantiate and return YourBean in case the property is false
}
}
In your case, you could add the #ConditionalOnProperty on top of your #Configuration (and no longer extend Condition).
#Configuration
#ConditionalOnProperty(name = "your.property", havingValue = "true")
public class Config {
// ...
}
Rely on extending Condition only if #ConditionalOnProperty is not flexible enough for your use case.

Defining an alias with java #Configuration class

I'm trying to imitate the behavior of the Spring XML alias in #Configuration class.
I have an alias in XML that looks like that:
<alias name="${com.some.bean}" alias="myBean"/>
I have a #Configuration class that looks like that:
#Configuration
public class MyConfig {
#Lazy
#Bean(name = "bean1")
public MyBean bean1() { return new MyBean(); }
#Lazy
#Bean(name = "bean2")
public MyBean bean2() { return new MyBean(); }
}
I want to load dynamically either bean1 or bean2 an give the one that's loaded the alias myBean. according to the resolution of com.some.bean property. It's easy to do with XMLs, yet I can't find the #Configuation equivalent.
Note: I do not want to use profiles because that's not how my system currently works and changing all the property resolution to use profiles instead of property files is not an option at the moment.
An ugly workaround is to define a #Bean which receives the property value as a #Value and performs the resolution itself
#Bean("myBean")
public MyBean myBean(#Value("${com.some.bean}") String value) {
if (value.equals("bean1"))
return bean1();
else if (value.equals("bean2"))
return bean2();
else {
throw new RuntimeException("nope, something went wrong");
}
}

Spring Dependency Injection: How to Instantiate Class based on system property?

consider this example
I have interface
interface FSClient
String getMeData()
and classes implementing it
class MockFSClient implements FSClient {
public String getMeData() {
return “I am mock”;
}
}
class RestFSClient implements FSClient {
public String getMeData() {
final String data = // from web service
return data;
}
}
and a manager which looks like
class FSManager {
private FSClient fsclient;
#Autowired
public void FSManager(#Nonnull final FSClient fsClient) {
this.fsclient = fsClient;
}
}
I want to instantiate fsclient based on a system property
com.fs.mock=true
meaning if com.fs.mock=true, fsclient should be MockFSClient else fsClient should be RestFSClient
How can I do that?
Why do I need it?
so that I can decouple and do testing
Please help, I am new to Spring
Are you asking how you get the value of com.fs.mock because the answer is use the #Value annotation and a PropertyPlaceholderConfigurer bean.
If you are asking how do you create the actual object then as #Jakkra says use a factory that contains an if statement around the value of com.fs.mock. Its not the most elegant solution but it would work.
Example
public class ClientFactory {
#Value("${com.fs.mock}")
private boolean mockFlag;
public static returnClientInstance(){
if(mockFlag){
return new MockFSClient();
}
else{
return new RestFSClient();
}
}
}
Use profiles
...
<beans profile="dev">
<bean id="b1" class="MockFSClient" />
</beans>
<beans profile="uat,prod">
<bean id="b1" class="RestFSClient" />
</beans>
...
... -Dspring.profiles.active=dev ...
Probably the best approach is to use profiles as described by Evgeniy Dorofeev in which case you wouldn't need your custom System property. But here's another solution if you were to stick to your custom System property using Conditional Bean Configuration.
In this approach you would need two custom Conditions to encapsulate your conditions:
public class MockFSCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String fsMockProperty = context.getEnvironment().getProperty("com.fs.mock");
return fsMockProperty!=null && fsMockProperty.toLowerCase().equals("true");
}
}
public class RestFSCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String fsMockProperty = context.getEnvironment().getProperty("com.fs.mock");
return fsMockProperty==null || !fsMockProperty.toLowerCase().equals("true");
}
}
In which case your configuration class would look like this:
#Configuration
#ComponentScan(basePackages = {"your.beans.package"})
public class Config {
#Bean
public FSClient mockFSClient(){
return new MockFSClient();
}
#Bean
public FSClient restFSClient(){
return new RestFSClient();
}
#Bean(name="fsManager")
#Conditional(MockFSCondition.class)
public FSManager mockFsManager() {
return new FSManager(mockFSClient());
}
#Bean(name="fsManager")
#Conditional(RestFSCondition.class)
public FSManager restFsManager() {
return new FSManager(restFSClient());
}
}
Notice that we have created two beans with the same name fsManager each one of which is annotated with the two conditions above.
So now at runtime Spring would look for that custom System prop and given whether it's there/correct or not it will instantiate the correct version of FSManager constructor-injected with the right FSClient.

Categories

Resources