Spring autowire of static configuration-object fails - java

as a Spring newbie I try to load external configuration into a Configuration-Object which I then try to autowire.
Following is my setup broken down to a bare minimum (less fields aso):
Package-structure:
app
|--Application.java
app.configuration
|--ConfigProperties.java
With my configuration.properties residing in:
src/main/resources/config
Application.java:
package app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import app.configuration.ConfigProperties;
#SpringBootApplication
public class Application
{
//just to test if autowire works
#Autowired
public static ConfigProperties config;
public static void main(String[] args)
{
SpringApplication.run(Application.class, args);
System.out.println(config.getId());
}
}
ConfigProperties.java:
package app.configuration;
import javax.validation.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.validation.annotation.Validated;
#Configuration
#PropertySource("classpath:/config/configuration.properties")
#ConfigurationProperties
#Validated
public class ConfigProperties
{
#NotBlank
private String id;
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
}
This results in a NullPointerException for the line:
System.out.println(config.getId());
in the Apllication.java.
I read several posts on this topic but most imply that own instantiation prevents autowiring, which I am not doing, or that the scan can't find the class to autowire, but from what I read with my annotations and package structure that should not be the problem.
Where is/are my mistake(s)?
PS: it seems that properties are read correctly from the property file. If I test the validation, the expected error is thrown for Null-value if leave out the part where I try to autowire.

#Autowired
public static ConfigProperties config;
You cannot autowire static fields in Spring.
//just to test if autowire works
You can write a new Junit test cases for that.
Something like:
#Autowired
public ConfigProperties config;
in your test class.

Since the annotation #Autowired requires getters/setters to inject the correct implementation, I don't suggest you to wire a static content. However, there is a workaround for this issue:
public static ConfigProperties config;
#Autowired
public Application(ConfigProperties config) {
Application.config= config;
}
Alternatively, take a look on the #PostConstruct annotation.

Do not use a static ConfigProperties . Then use #PostConstruct to print the ID from your config object.
#Autowired
ConfigProperties config;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#PostConstruct
void printId(){
System.out.println("ID = " + config.getId());
}

Related

Spring Boot - creating Bean as Singleton in IOC container

Bean creation is supposed to be a Singleton in the spring container, correct? I am migrating a Spring configuration file to Spring Boot with Annotations. I have the below code, but it seems to call the "mySingletonBean()" every time that it is used within another bean creation. It is my understanding that the #Bean annotation is supposed to be Singleton by default. Am I creating my Beans correctly?
#Bean
public SomeBean mySingletonBean() {
SomeBean mybean = new SomeBean();
mybean.setName = "Name";
return mybean;
}
#Bean
public Bean1 bean1() {
Bean1 bean1 = new Bean1();
bean1.setBean(mySingletonBean());
return bean1;
}
#Bean
public Bean2 bean2() {
Bean2 bean2 = new Bean2();
bean2.setBean(mySingletonBean());
return bean2;
}
Spring is proxying your application context class, and takes care of all the context related stuff like instantiating, caching etc. of the beans.
Run this small test, feel free to debug to see what class the configuration class became:
package stackoverflow;
import java.util.Arrays;
import java.util.Date;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertTrue;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Test.MyContext.class)
public class Test {
#Autowired
private ApplicationContext applicationContext;
#Autowired
#Qualifier("myStringBean")
private String myStringBean;
#Autowired
private SomeBean someBean;
#Configuration
static class MyContext {
#Bean
public String myStringBean() {
return String.valueOf(new Date().getTime());
}
#Bean
public SomeBean mySomeBean() {
return new SomeBean(myStringBean());
}
}
#org.junit.Test
public void test() {
assertTrue(myStringBean == applicationContext.getBean("myStringBean"));
assertTrue(myStringBean == someBean.getValue());
System.out.println(Arrays.asList(applicationContext.getBean("myStringBean"), myStringBean, someBean.getValue()));
}
static class SomeBean {
private String value;
public SomeBean(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}
Spring Boot is smart framework and method mySingletonBean() will starts only once time, don't worry about it. U can start debug mode and see .

Configuration properties are not loaded in Spring Test

I have a Spring Boot application which has some configuration properties. I'm trying to write a test for some of the components and want to load configuration properties from a test.properties file. I can't get it to work.
Here's my code:
test.properties file (under src/test/resources):
vehicleSequence.propagationTreeMaxSize=10000
Configuration properties class:
package com.acme.foo.vehiclesequence.config;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
#Component
#ConfigurationProperties(prefix = VehicleSequenceConfigurationProperties.PREFIX)
public class VehicleSequenceConfigurationProperties {
static final String PREFIX = "vehicleSequence";
#NotNull
private Integer propagationTreeMaxSize;
public Integer getPropagationTreeMaxSize() {
return propagationTreeMaxSize;
}
public void setPropagationTreeMaxSize(Integer propagationTreeMaxSize) {
this.propagationTreeMaxSize = propagationTreeMaxSize;
}
}
My test:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = VehicleSequenceConfigurationProperties.class)
#TestPropertySource("/test.properties")
public class VehicleSequenceConfigurationPropertiesTest {
#Autowired
private VehicleSequenceConfigurationProperties vehicleSequenceConfigurationProperties;
#Test
public void checkPropagationTreeMaxSize() {
assertThat(vehicleSequenceConfigurationProperties.getPropagationTreeMaxSize()).isEqualTo(10000);
}
}
The test fails with "Expecting actual not to be null" meaning the property propagationTreeMaxSize in the configuration properties class was not set.
Two minutes after posting the question, I've found the answer.
I had to enable configuration properties with #EnableConfigurationProperties(VehicleSequenceConfigurationProperties.class):
#RunWith(SpringRunner.class)
#TestPropertySource("/test.properties")
#EnableConfigurationProperties(VehicleSequenceConfigurationProperties.class)
public class VehicleSequenceConfigurationPropertiesTest {
#Autowired
private VehicleSequenceConfigurationProperties vehicleSequenceConfigurationProperties;
#Test
public void checkPropagationTreeMaxSize() {
assertThat(vehicleSequenceConfigurationProperties.getPropagationTreeMaxSize()).isEqualTo(10000);
}
}

Unit testing a AWS Spring Boot Configuration file - possible?

We are currently using Spring Boot to connect to a mocked local instance of Amazon SQS. The application itself is working when run, but we would like to try and test the SQS Config class, if possible and if it makes sense.
Here is the configuration class. All properties are pulled from the typical application.properties file when the Spring application itself is run.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AWSSQSConfig {
#Value("${aws.sqs.endpoint}")
private String AWSSqsEndpoint;
// Producer QueueMessageTemplate
#Bean
public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSqs, ResourceIdResolver resourceIdResolver) {
if (!AWSSqsEndpoint.isEmpty())
amazonSqs.setEndpoint(AWSSqsEndpoint);
return new QueueMessagingTemplate(amazonSqs, resourceIdResolver);
}
}
Here is the test class. We are attempting to pass the configuration in via TestPropertySource, but they don't actually seem to get to the AWSSQSConfig class. AWSSqsEndpoint inside the instance of the class is always NULL.
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.lonewolf.formsbuilder.config.AWSSQSConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.core.QueueMessagingTemplate;
import org.springframework.test.context.TestPropertySource;
import static org.junit.Assert.assertNotNull;
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#TestPropertySource(properties = {
"cloud.aws.region.static=us-east-1",
"cloud.aws.credentials.accessKey=zzzzz",
"cloud.aws.credentials.secretKey=zzzzzz",
"aws.sqs.endpoint = http://localhost:9324",
"aws.sqs.requestQueue = CreateSchemaRequest",
"aws.sqs.responseQueue = CreateSchemaResponse"
})
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#Test
public void contextLoads() {
AWSSQSConfig config = new AWSSQSConfig();
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Is this a chicken and the egg situation, where the spring framework actually needs to run first to inject those config values? Do we need an integration test here instead?
EDIT with working solution...
Using the accepted answer, here is my working test! I was able to remove my dependency of the Spring framework.
#RunWith(MockitoJUnitRunner.class)
public class AWSSQSConfigTests {
#Mock
private AmazonSQSAsync amazonSqs;
#Mock
private ResourceIdResolver resourceIdResolver;
#InjectMocks
private AWSSQSConfig config;
#Before
public void setup() {
ReflectionTestUtils.setField(config, "AWSSqsEndpoint", "http://fake");
}
#Test
public void contextLoads() {
QueueMessagingTemplate queueMessagingTemplate = config.queueMessagingTemplate(amazonSqs, resourceIdResolver);
assertNotNull("The response body must not be null", queueMessagingTemplate);
}
}
Have you tried injecting mock to your class (or autowire it), and then setting that field it using ReflectionTestUtils? This is a nice test utils class that Spring provides that allows you to do something like what you want without doing code modifications.
I mean something like this:
#InjectMocks
private AWSSQSConfig awssqsConfig;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(awssqsConfig, "AWSSqsEndpoint", "putYourEndpointHere");
}

Spring boot #Value not working for static field

I am trying to get value from a property file on spring boot.
application.properties file is under resources folder, and its content;
TEST=someText
And the code is;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
#SpringBootApplication
#PropertySource("classpath:application.properties")
public class Bb8Application {
#Value("${TEST}")
static String someString;
public static void main(String[] args) {
System.out.print(someString);
}
}
I get NULL as a result instead of "someText". Is there something that I am missing?
Spring does not allow injecting to static fields. If you really want to use static variable you can try this workaround.
You can use #Value annotation for static fields.
#Component
public class MyClass {
private static String MY_STATIC_FIELD;
public MyClass(#Value("${TEST}") String input) {
MY_STATIC_FIELD = input;
}
}
more for information: https://www.baeldung.com/spring-inject-static-field

Spring boot + spring batch without DataSource

I'm trying to configure spring batch inside spring boot project and I want to use it without data source. I've found that ResourcelessTransactionManager is the way to go but I cannot make it work. Problem is I already have 3 another dataSources defined, but I don't want to use any of them in springBatch.
I've checked default implementation DefaultBatchConfigurer and if it is not able to find dataSource it will do exactly what I want. Problem is I've 3 of them and dont want to use any.
Please dont suggest to use hsql or other in memory DB as I dont want that.
I got around this problem by extending the DefaultBatchConfigurer class so that it ignores any DataSource, as a consequence it will configure a map-based JobRepository.
Example:
#Configuration
#EnableBatchProcessing
public class BatchConfig extends DefaultBatchConfigurer {
#Override
public void setDataSource(DataSource dataSource) {
//This BatchConfigurer ignores any DataSource
}
}
In my case I persist data to Cassandra. If you are using spring-boot-starter-batch it is expected to provide a DataSource which is not yet implemented but you can trick the configuration like in the following steps:
Step1:
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class SampleSpringBatchApplication{
public static void main(String[] args) {
System.setProperty("spring.devtools.restart.enabled", "true");
SpringApplication.run(SampleSpringBatchApplication.class, args);
}
}
Step2:
#Configuration
#EnableBatchProcessing
public class SampleBatchJob extends DefaultBatchConfigurer {
//..
#Override
public void setDataSource(DataSource dataSource) {
}
//..
}
You can try excluding the DataSourceAutoConfiguration in #SpringBootApplication. See the sample code below.
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
#EnableBatchProcessing
public class SampleBatchApplication {
#Autowired
private JobBuilderFactory jobs;
#Autowired
private StepBuilderFactory steps;
#Bean
protected Tasklet tasklet() {
return new Tasklet() {
#Override
public RepeatStatus execute(StepContribution contribution, ChunkContext context) {
return RepeatStatus.FINISHED;
}
};
}
#Bean
public Job job() throws Exception {
return this.jobs.get("job").start(step1()).build();
}
#Bean
protected Step step1() throws Exception {
return this.steps.get("step1").tasklet(tasklet()).build();
}
public static void main(String[] args) throws Exception {
System.exit(SpringApplication.exit(SpringApplication.run(SampleBatchApplication.class, args)));
}
}
And sample test class
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.rule.OutputCapture;
import static org.assertj.core.api.Assertions.assertThat;
public class SampleBatchApplicationTests {
#Rule
public OutputCapture outputCapture = new OutputCapture();
#Test
public void testDefaultSettings() throws Exception {
assertThat(SpringApplication.exit(SpringApplication.run(SampleBatchApplication.class))).isEqualTo(0);
String output = this.outputCapture.toString();
assertThat(output).contains("completed with the following parameters");
}
}
If you have more than one DataSource in your configuration (regardless of if you want to use them or not) you need to define your own BatchConfigurer. It's the only way the framework knows what to do in situations like that.
You can read more about the BatchConfigurer in the documentation here: http://docs.spring.io/spring-batch/trunk/apidocs/org/springframework/batch/core/configuration/annotation/BatchConfigurer.html
We had the similar problem, we were using spring boot JDBC and we did not want to store spring batch tables in the DB, but we still wanted to use spring's transaction management for our DataSource.
We ended up implementing own BatchConfigurer.
#Component
public class TablelessBatchConfigurer implements BatchConfigurer {
private final PlatformTransactionManager transactionManager;
private final JobRepository jobRepository;
private final JobLauncher jobLauncher;
private final JobExplorer jobExplorer;
private final DataSource dataSource;
#Autowired
public TablelessBatchConfigurer(DataSource dataSource) {
this.dataSource = dataSource;
this.transactionManager = new DataSourceTransactionManager(this.dataSource);
try {
final MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(this.transactionManager);
jobRepositoryFactory.afterPropertiesSet();
this.jobRepository = jobRepositoryFactory.getObject();
final MapJobExplorerFactoryBean jobExplorerFactory = new MapJobExplorerFactoryBean(jobRepositoryFactory);
jobExplorerFactory.afterPropertiesSet();
this.jobExplorer = jobExplorerFactory.getObject();
final SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
simpleJobLauncher.setJobRepository(this.jobRepository);
simpleJobLauncher.afterPropertiesSet();
this.jobLauncher = simpleJobLauncher;
} catch (Exception e) {
throw new BatchConfigurationException(e);
}
}
// ... override getters
}
and setting up initializer to false
spring.batch.initializer.enabled=false

Categories