Spring Boot: custom AutoConfiguration class fails to find properties - java

I have a library for a FooService.
Here's it's AutoConfiguration class:
#Configuration
#EnableConfigurationProperties(FooProperties.class)
public class FooServiceAutoConfigure {
#Autowired
FooProperties config;
#Bean
public FooService getFooService() throws Exception {
return new FooService(config.username, config.password)
}
}
Here's the FooProperties class:
#ConfigurationProperties(prefix = "foo")
public class FooProperties {
String username;
String password;
}
In resources/META-INF/spring.factories I have the following line:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.my.company.FooServiceAutoConfigure
In my main project (which is a #SpringBootApplication), I have:
A Maven dependency to this library.
foo.username and foo.password in application.properties
Some #Autowired FooService.
I can see in the logs that Spring Boot is trying to instantiate FooService but the config.username and config.password are nulls. Why?

Solved.
I'll answer for future readers:
There weren't setters for FooProperties

Related

Testing #ConfigurationProperties annotation without loading Spring context

I'm trying to test a class that has the #ConfigurationProperties annotation but without loading the entire Spring context. I tried using only the JUnit5's features in order to do that but until now didn't succeed in that.
I'm using spring-boot-starter-parent v2.6.2 .
The class I'm testing :
#ConfigurationProperties("db.mongo")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class MongoProperties {
private String host;
private String db;
private String user;
private String password;
}
The application.yaml :
db:
mongo:
host: localhost
db: test
user: test-user
password: secret
My Test class :
#ExtendWith(SpringExtension.class)
#EnableConfigurationProperties({MongoProperties.class})
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Test
public void mongoPropertiesLoadedTest(){
assertNotNull(properties.getDb());
assertNotNull(properties.getHost());
assertNotNull(properties.getPassword());
assertNotNull(properties.getUser());
}
}
The MongoProperties bean is injected successfully, but all the values inside are null and the asserts fail.
Adding the #SpringBootTest solves the issue of the null values in the instance of the bean, but it also starts the whole spring context which is what I don't want.
But using #ExtendWith(SpringExtension.class) will also start the spring context. The difference is that it starts the context in a traditional way but #SpringBootTest starts it in a spring-boot way. So no matter you use which of them , it still requires to start the spring context.
If your concern is to minimise the number of beans required to be loaded into the spring context when using #SpringBootTest, you can actually configure a specified #Configuration like the following as by default #SpringBootTest will load all beans defined in your applications which may be too much for testing (see this for details) :
#SpringBootTest
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
If you really want to just use #ExtendWith(SpringExtension.class) , you will lose the spring-boot feature such as externalising configuration features which cause you cannot load properties from application.properties and cannot support loading properties from YAML file etc. You have to manually configure ConfigDataApplicationContextInitializer to enable such features :
#ExtendWith(SpringExtension.class)
#ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
You can consider to further use #SpringJUnitConfig to combine #ExtendWith(SpringExtension.class) and #ContextConfiguration together which gives you :
#SpringJUnitConfig(initializers = ConfigDataApplicationContextInitializer.class)
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
Actually both approaches do not have much differences in term of speed , so I prefer to just use #SpringBootTest for simplicity as it does not requires you to configure ConfigDataApplicationContextInitializer.

Java Spring Boot #Autowired values are null

When I try to print the variable that I have autowired, it prints "null" instead of the value I set it to, "Example." I can't quite figure out what I'm missing
In my AppConfig class I have:
#Configuration
public class ApplicationConfiguration {
#Bean
public String tableName(){
return "Example";
}
}
In my other class, DAOMethods, that I want to autowire the variable in:
#Component
public class DAOMethods {
#Autowired
private String tableName;
public void print(){
System.out.println(tableName);
}
}
They exist in different packages; With AppConfig living in a config
folder and DAOMethods in client->dynamodb->util folder. Config and
Client are folders under the main->java folder
The added #Configuration annotation scans for the beans in the current and its subpackages. You need to explicitly tell the application to scan the required packages. So you can do:
#SpringBootApplication (scanBasePackages = {"config", "client"})
OR,
You need to keep the config and other classes which uses that config in same root package. You can put the config folder and client folder under same package, say com.application and then add the following in your main class:
#SpringBootApplication(scanBasePackages = "com.application")
Now run the application.
You problem have many solutions
F.e. you can create config-class with required set of parameters and next autowire it (Injecting values with #Value annotation from application configuration file is good practice):
#Component
public class CustomConfiguration {
#Value("${table.name}")
private String tableName;
#Value("${some.value}")
private Integer someValue;
public String getTableName() {
return tableName;
}
public Integer getsomeValue() {
return someValue;
}
}
And you application.properties will looks like:
some.value=1
table.name=Example
Or you can simply inject single value from configuration with #Value annotation
One of solutions is using bean name in #Value annotation:
#Configuration
public class ApplicationConfiguration {
#Bean
public String tableName(){
return "Example";
}
}
#Component
public class DAOMethods {
#Value(#{tableName})
private String tableName;
}
More examples you can see in this question: Autowire a string from Spring #Configuration class?

Test on Spring configuration properties reading an external YML file doesn't work

I'm working with Spring Boot 2.4.8, and I'm reading into a bean the information read from an external YML file:
#Component
#ConfigurationProperties(prefix = "my.conf")
#PropertySource(value = "classpath:ext.yml", factory = YamlPropertySourceFactory.class)
public class MyExternalConfProp {
private String property;
public void setProperty(String property) {
this.property = property;
}
public String getProperty() {
return property;
}
}
I defined a custom factory to read external YML files, as stated here in the article #PropertySource with YAML Files in Spring Boot:
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(
Objects.requireNonNull(encodedResource.getResource().getFilename()),
Objects.requireNonNull(properties));
}
}
The content of the YML file is the following:
my.conf.property: yeyeye
The problem is that I cannot find a proper slice to test the configuration property in isolation. In fact, the following test fails:
#SpringBootTest(classes = {MyExternalConfProp.class})
class MyExternalConfPropTest {
#Autowired
private MyExternalConfProp confProp;
#Test
void externalConfigurationPropertyShouldBeLoadedIntoSpringContext() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}
As we said, the test fails with the following message:
java.lang.AssertionError:
Expecting
<in.rcard.externalconfprop.MyExternalConfProp#4cb40e3b>
to have a property or a field named <"property"> with value
<"yeyeye">
but value was:
<null>
Whereas, if I don't use any slice, the test succeeds:
#SpringBootTest
class ExternalConfPropApplicationTests {
#Autowired
private MyExternalConfProp confProp;
#Test
void contextLoads() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}
How can I resolve this? Is it some initializer or something similar that I can add to the slice to make the test succeed?
Here you can find the whole project on GitHub.
Add #EnableConfigurationProperties to your test or start the spring boot application on your test will solve the problem
#EnableConfigurationProperties
#SpringBootTest(classes = {MyExternalConfProp.class})
class MyExternalConfPropTest {
#Autowired
private MyExternalConfProp confProp;
#Test
void externalConfigurationPropertyShouldBeLoadedIntoSpringContext() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}
or
#SpringBootTest(classes = {YourSpringBootApplication.class})
class MyExternalConfPropTest {
#Autowired
private MyExternalConfProp confProp;
#Test
void externalConfigurationPropertyShouldBeLoadedIntoSpringContext() {
assertThat(confProp).hasFieldOrPropertyWithValue("property", "yeyeye");
}
}

junit spring boot profile properties not being read

I am trring to read a value from the properties file in my junit setup using spring boot.
I can not read the value. Below is my content:-
application-test.properties
my.user.name=Amar
COnfig file to create beans:
#Configuration
#ActiveProfiles("test")
#Profile("test")
public class RdbmsTestConfig {
#Value("${my.user.name}")
private String name;
#Bean
public String myString(){
return "Amar";
}
#Bean
public PropsHolder propsHolder() {
PropsHolder propsHolder = new PropsHolder();
propsHolder.setUserName(name);
return propsHolder;
}
}
My test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = RdbmsTestConfig.class)
#ActiveProfiles("test")
public class TestRoomService {
#Autowired
#Qualifier("myString")
private String myString;
#Autowired
private PropsHolder propsHolder;
#Autowired
private Environment env;
#Test
public void userTest() {
Arrays.stream(env.getActiveProfiles()).forEach(System.out::println);
System.out.println(propsHolder.getUserName());
Assert.assertNotNull(myString);
Assert.assertEquals("Amar",myString);
}
}
The value for propsHolder.getUserName comes out to be ${my.user.name}
First remove #ActiveProfiles("test") from your class RdbmsTestConfig. Then your test just defines the RdbmsTestConfig as spring context. As I can see you do not run a real spring boot test. The problem is you do not have any PropertyPlaceholderConfigurer configured in your spring config. So either configure one PropertyPlaceholderConfigurer or add #SpringBootTest to your test if you have any SpringBootApplication.
I've never used #Profile(), so i'm not sure if that is supposed to do what you want it to do. But I'm always using #PropertySources(), because otherwise, how is the code supposed to know where to look for the properties?
#Configuration
#PropertySources(value = { #PropertySource("classpath:core-
test.properties") })
I created a base test class that has the required annotations:-
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = RdbmsTestConfig.class)
#ActiveProfiles("test")
#SpringBootTest
public abstract class BastTest { }
#ActiveProfiles set the profile to use used, I dont have to mention it in the application.properties file
My test class now extends this:-
public class TestRoomService extends BastTest
In my RdbmsTestConfig remove #ActiveProfiles annotation.

Spring Boot Autowired null

I have several classes in a Spring Boot project, some work with #Autowired, some do not. Here my code follows:
Application.java (#Autowired works):
package com.example.myproject;
#ComponentScan(basePackages = {"com.example.myproject"})
#Configuration
#EnableAutoConfiguration
#EnableJpaRepositories(basePackages = "com.example.myproject.repository")
#PropertySource({"classpath:db.properties", "classpath:soap.properties"})
public class Application {
#Autowired
private Environment environment;
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
#Bean
public SOAPConfiguration soapConfiguration() {
SOAPConfiguration SOAPConfiguration = new SOAPConfiguration();
SOAPConfiguration.setUsername(environment.getProperty("SOAP.username"));
SOAPConfiguration.setPassword(environment.getProperty("SOAP.password"));
SOAPConfiguration.setUrl(environment.getProperty("SOAP.root"));
return SOAPConfiguration;
}
HomeController (#Autowired works):
package com.example.myproject.controller;
#Controller
class HomeController {
#Resource
MyRepository myRepository;
MyService (#Autowired does not work):
package com.example.myproject.service;
#Service
public class MyServiceImpl implements MyService {
#Autowired
public SOAPConfiguration soapConfiguration; // is null
private void init() {
log = LogFactory.getLog(MyServiceImpl.class);
log.info("starting init, soapConfiguration: " + soapConfiguration);
url = soapConfiguration.getUrl(); // booom -> NullPointerException
I do not get the SOAPConfiguration but my application breaks with a null pointer exception when I try to access it.
I have already read many Threads here and googled around, but did not find a solution yet. I tried to deliver all necessary information, please let me know if anything misses.
I guess you call init() before the autowiring takes place. Annotate init() with #PostConstruct to make it call automatically after all the spring autowiring.
EDIT: after seeing your comment, I guess you are creating it using new MyServiceImpl(). This takes away the control of the MyServiceImpl from Spring and gives it to you. Autowiring won't work in those case
Did you created a bean for the class SOAPConfiguration in any of your configuration classes? If you want to autowire a class in your project, you need to create a bean for it. For example,
#Configuration
public class SomeConfiguration{
#Bean
public SOAPConfiguration createSOAPConfiguration(){
return new SOAPConfiguration();
}
}
public class SomeOtherClass{
#Autowired
private SOAPConfiguration soapConfiguration;
}

Categories