Spring Boot: substitute #Configuration class with another - java

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();
}

Related

Respect #Lazy annotation on non-#Primary #Bean

I'm having problems getting Spring to respect the #Lazy annotation on #Bean methods when it is configured to use a different #Bean method that returns an implementation of the same interface that is flagged as #Primary.
Specifically, I have a #Configuration-annotated class with several #Bean methods that all return the same interface. Many of these #Bean methods are #Lazy, as they contact external services for which the application may not currently be using. The #Primary bean is not #Lazy, as it looks at runtime configuration to determine which implementation to return.
Here is a contrived example of that configuration class, revolving around a fictitious ThingService interface:
#Configuration
#ComponentScan(basePackages = { "com.things" })
public class ThingConfiguration {
#Bean
public ThingOptions thingOptions() {
ThingOptions options = new ThingOptions();
options.sharing = true;
return options;
}
#Primary
#Bean
public ThingService primaryThing(ThingOptions options, ApplicationContext context) {
System.out.println("PrimaryThing -- Initialized");
if (options.sharing) {
return context.getBean("OurThing", ThingService.class);
} else {
return context.getBean("YourThing", ThingService.class);
}
}
#Lazy
#Bean(name = "YourThing")
public ThingService yourThing() {
System.out.println("YourThingService -- Initialized");
return new YourThingService();
}
#Lazy
#Bean(name = "OurThing")
public ThingService ourThing() {
System.out.println("OurThingService -- Initialized");
return new OurThingService();
}
}
I then have a #Component that depends on this interface which that the #Primary annotation will ensure that the correct implementation will be injected into the object. Here is an example of that downstream #Component:
#Component
public class ThingComponent {
private final ThingService thingService;
#Inject
public ThingComponent(ThingService thingService) {
this.thingService = thingService;
}
}
I then built a small test to ensure that #Lazy and #Primary are all being respected.
public class ThingTest {
#Test
public void TestLazyAndPrimary() {
// Arrange
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ThingConfiguration.class);
context.refresh();
// Act
ThingComponent component = context.getBean(ThingComponent.class);
// Assert
Assert.assertNotNull(component);
}
}
However, when I run this test, I found that #Lazy was being ignored. The following text is emitted to the console:
PrimaryThing -- Initialized
OurThingService -- Initialized
YourThingService -- Initialized
The "YourThing" #Bean should not have been initialized, as it was #Lazy and not loaded at runtime via the ApplicationContext.getBean() method. Yet when the ThingComponent is resolved, it causes the #Bean methods with that return an implementation of ThingService to be hydrated before the #Primary mean is chosen.
How do I get the #Primary annotated implementation of an interface to be respected without causing all of the non-#Primary implementations annotated with #Lazy to be hydrated?
I have been unable to stop the #Primary annotation from forcing eager hydration of all #Bean methods that return that interface, even though this information seems available without forcing hydration from the annotations in exclusivity. I got around this by using a naming convention on #Bean methods instead.
Specifically, I changed my #Primary annotated #Bean method to include a name like so:
#Configuration
#ComponentScan(basePackages = { "com.things" })
public class ThingConfiguration {
// #Primary -- I don't want someone to accidentally use this without a #Qualifier!
#Bean(name = "PrimaryThingService")
public ThingService primaryThing(ThingOptions options, ApplicationContext context) {
System.out.println("PrimaryThing -- Initialized");
if (options.sharing) {
return context.getBean("OurThing", ThingService.class);
} else {
return context.getBean("YourThing", ThingService.class);
}
}
// ... the rest of the methods removed for clarity ...
}
Then I placed a #Qualifier on the ThingService being injected into the #Component like so:
#Component
public class ThingComponent {
private final ThingService thingService;
#Inject
public ThingComponent(#Qualifier("PrimaryThingService") ThingService thingService) {
this.thingService = thingService;
}
}
Now when I rerun the test, I get the following output:
PrimaryThing -- Initialized
OurThingService -- Initialized
So this removes the #Primary annotation in place of using a named #Bean following a convention of "Primary{Interface}", stepping around the Spring's overeager hydration of non-#Primary annotated #Bean methods.

how to override custom auto-configurations?

I have made a auto-configuration class for my project to connect to AWS Sqs. This class is working fine but when I try to override auto-configuration functionality, I am getting autowire error from calling code. Please guide if my implementation of auto-configuration class is right?
I tried different #Conditional annotations to find the solution but its not working out.
#Configuration
#ConditionalOnClass(AwsSqsAsyncClient.class)
public class AwsSqsAsyncAutoConfiguration {
#Configuration
#ConditionalOnProperty(name = "aws.sqs.queue-type", havingValue = "fifo")
#EnableConfigurationProperties(AwsSqsProperties.class)
static class AwsFifoSqsAsyncAutoConfigurationBuilder{
private final AwsSqsProperties awsSqsProperties; //another class defined by me for properties
#Inject
public AwsFifoSqsAsyncAutoConfigurationBuilder(AwsSqsProperties awsSqsProperties) {
this.awsSqsProperties = awsSqsProperties;
}
#Bean
#ConditionalOnMissingBean
public AwsSqsAsyncClient fifoAsyncClient() {
return AwsSqsFactory.createAwsSqsAsyncClient(AwsSqsMessageRequestFactory
.createAwsSqsFifoRequestFactory("producer-application-name", awsSqsProperties.getQueueUrl()),
awsSqsProperties.getAccessKey(), awsSqsProperties.getSecretKey());
}
}
}
This is where I am trying to override auto-configuration functionality
#Configuration
public class AmazonSQSConfig {
#Bean
public AwsSqsAsyncClient amazonSqsAsyncClient(){
return AwsSqsFactory
.createAwsSqsAsyncClient(AwsSqsMessageRequestFactory
.createAwsSqsFifoRequestFactory("someother-producer-application-name",
"some amazon url"), "access key",
"secret key");
}
}
calling code, this is where I am trying to autowire and getting error
"Could not autowire. there is more than one bean of type AwsSqsAsyncClient
Beans: amazonSqsAsyncClient
and fifoAsyncClient "
private final AwsSqsAsyncClient awsSqsClient;
#Autowired
public SQSPublisherImpl(AwsSqsAsyncClient awsSqsClient) {
this.awsSqsClient = awsSqsClient;
}
Spring boot factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.aws.starter.sqs.AwsSqsAsyncAutoConfiguration
I expect autoconfiguration to get disabled when I define my bean for AwsSqsAsyncClient in calling code

Spring Cucumber Set Application Properties for Beans

I Have a bean defined in a configuration class:
#Configuration
public class Config {
#Value("${some-property}")
private String someProperty;
#Bean
public SomeBean someBean() {
return new SomeBean(someProperty);
}
}
How do I define a cucumber step definition to set the property some-property and let SomeBean to use it?
The problem I'm facing is that my beans are initialized before any steps happen. How do I reinitialize a bean, or refresh using the #RefreshScope maybe? Or can I start/restart the spring context in a later step?
Cucumber step:
#Given("I have some property set to (.*)")
public void someProperty(String someProperty) {
// Answer
}
And here's the empty step definition class to start Spring Context:
#SpringBootTest(classes = Application.class)
#DirtiesContext
#ActiveProfiles("it")
#AutoConfigureCache
#AutoConfigureTestEntityManager
#AutoConfigureWebMvc
#AutoConfigureMockMvc(secure = false)
#ImportAutoConfiguration
public class CucumberSpringContextBootstrapper implements En {
}

How to add a bean in SpringBootTest

The question seems extremely simple, but strangely enough I didn't find a solution.
My question is about adding/declaring a bean in a SpringBootTest, not overriding one, nor mocking one using mockito.
Here is what I got when trying the simplest implementation of my real need (but it doesn't work):
Some service, bean, and config:
#Value // lombok
public class MyService {
private String name;
}
#Value // lombok
public class MyClass {
private MyService monitoring;
}
#Configuration
public class SomeSpringConfig {
#Bean
public MyClass makeMyClass(MyService monitoring){
return new MyClass(monitoring);
}
}
The test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class })
public class SomeSpringConfigTest {
private String testValue = "testServiceName";
// this bean is not used
#Bean
public MyService monitoringService(){ return new MyService(testValue); }
// thus this bean cannot be constructed using SomeSpringConfig
#Autowired
public MyClass myClass;
#Test
public void theTest(){
assert(myClass.getMonitoring().getName() == testValue);
}
}
Now, if I replace the #Bean public MyService monitoring(){ ... } by #MockBean public MyService monitoring;, it works. I find it strange that I can easily mock a bean, but not simply provide it.
=> So how should I add a bean of my own for one test?
Edit:
I think ThreeDots's answer (create a config test class) is the general recommendation.
However, Danylo's answer (use #ContextConfiguration) fit better to what I asked, i.e. add #Bean directly in the test class.
Spring Test needs to know what configuration you are using (and hence where to scan for beans that it loads). To achieve what you want you have more options, the most basic ones are these two:
Create configuration class outside the test class that includes your bean
#Configuration
public class TestConfig {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
and then add it to to test as configuration class #SpringBootTest(classes = { SomeSpringConfig.class, TestConfig.class })
or
If you only need to use this configuration in this particular test, you can define it in static inner class
public class SomeSpringConfigTest {
#Configuration
static class ContextConfiguration {
#Bean
public MyService monitoringService() {
return new MyService();
}
}
}
this will be automatically recognized and loaded by spring boot test
Simply add the config as
#ContextHierarchy({
#ContextConfiguration(classes = SomeSpringConfig.class)
})
What i am using in this cases is #Import:
#DataJpaTest(showSql = false)
//tests against the real data source defined in properties
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Import(value = {PersistenceConfig.class, CustomDateTimeProvider.class})
class MessageRepositoryTest extends PostgresBaseTest {
....
Here i am using a pre configured "test slice".
In this case a need to add my JpaAuditingConfig.
But why not just adding the other beans as you did with your SomeSpringConfig.class ?:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { SomeSpringConfig.class, OtherBean.class })
public class SomeSpringConfigTest {
...
Everything listed in test will be injectable directly, all not declared must be added as mocks.

Instantiate a spring bean conditionally based on a property placeholder

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();
}

Categories

Resources