Springboot - #ConfigurationProperties not populating the class fields - java

I know similar questions have been asked many times with different variations. I have tried to learn from them and from the available tutorials and try to get it working but I think I am missing something that I am not able to figure out.
I want to load a configuration from the external yaml file admin-config.yml, and not the default application.yml in my case.
This is what I have done so far.
Annotated the Application file with #EnableConfigurationProperties
#SpringBootApplication
#EnableConfigurationProperties
public class MyServer {
public static void main(String[] args) {
SpringApplication.run(MyServer.class, args);
}
}
The AdminConfig component class:
#Component
#PropertySource("classpath:admin-config.yml")
#ConfigurationProperties(prefix = "admin-config")
public class AdminConfig {
private List<PrimeModerator> primeModerators;
#PostConstruct
public void init() {
System.out.println(primeModerators); // this is null
}
public void setPrimeModerators(List<PrimeModerator> primeModerators) {
this.primeModerators = primeModerators;
}
public List<PrimeModerator> getPrimeModerators() {
return primeModerators;
}
}
The PrimeModerator class
#Data
public class PrimeModerator implements Serializable {
private long id;
private String name;
private List<String> roles;
private String details;
private Date created;
}
The admin-config.yml.
admin-config:
primeModerators:
- !!com.mydomain.model.PrimeModerator
id: 1
name: Pawan
roles: [super-admin]
- !!com.mydomain.model.PrimeModerator
id: 2
name: Prashant
roles: [admin, moderator]
I have created a unit test case which always fails (which should not).
#SpringBootTest
#RunWith(SpringRunner.class)
public class AdminConfigTest {
#Autowired
private AdminConfig adminConfig;
#Test
public void testAdminConfig() {
assertNotNull(adminConfig.getPrimeModerators()); // this fails
}
}
I am not sure what I am missing here. Using springboot version 2.2.1.RELEASE with jdk8.
Thanks for your answers in advance!

from this documentation :
Baeldung - Spring
It says :
"The Hibernate Validation framework uses standard Java bean getters and setters, so it's important that we declare getters and setters for each of the properties."
Could you please test by adding getters/settest in PrimeModerator ?
Did you try with a single simple String value before you nested array ?
Good luck...
A github sample on Baeldung in the bottom of page

Related

Multiple instances of a bean of one class filled with values from application.properties

I intend to write some HealtCheckContributors for a Spring Boot application using spring-boot-actuator. Hence, I implemented two of them. they are intended for checking the health of different apps, of course, but have a nearly identical structure, except the configuration properties, ...
SonarQube complains about that and I wonder if it is possible to have a single health check class but instantiated as many times as defined in application.properties.
An example:
application.properties:
# actuator
app1.management.baseUrl=http://localhost:10000
app1.management.name=app1HealthCheckContributor
app2.management.basUrl=http://localhost:10001
app2.management.name=app2HealthCheckContributor
HealthCheckContributor for app1:
#Slf4j
#Component("xxx")
public class App1HealthCheckContributor extends AbstractHealthIndicator {
private final App1Properties app1Properties;
public App1HealthCheckContributor(final App1Properties app1Properties) {
this.app1Properties = app1Properties;
}
#Override
protected void doHealthCheck(Health.Builder builder) {...}
}
...and this code for each HealthCheckContributor only distinct in its appXProperties.
Isn't it possible to have some kind of base class like:
#Slf4j
#Component()
public class MyHealthCheckContributor extends AbstractHealthIndicator {
private final MyProperties myProperties;
public MyHealthCheckContributor(final MyProperties myProperties) {
this.myProperties = myProperties;
}
#Override
protected void doHealthCheck(Health.Builder builder) {...}
}
and let Spring Boot take care of instantiating two HealthCheckContributors (in our case App1HealthCheckContributor and App2HealthCheckContributor)?
This would eliminate code duplication.
An example of the properties class file:
#Slf4j
#Data
#ConfigurationProperties(prefix = "app1.management")
public class App1Properties {
private String baseUrl;
private String ...;
}
How can I achieve this and how must an application.properties file looks like to achieve what I intend to do?
The final question: How to test multiple instance creation of a bean of one class filled with values from application.properties?
Assuming the code in doHealthCheck is exactly the same for all apps to be checked you could do the following.
You would start by creating a single health check class:
#Slf4j
public class AppHealthCheckContributor extends AbstractHealthIndicator {
private final AppProperties appProperties;
public App1HealthCheckContributor(final AppProperties appProperties) {
this.appProperties = appProperties;
}
#Override
protected void doHealthCheck(Health.Builder builder) {...}
}
And the properties model as follows:
#Slf4j
#Data
public class AppProperties {
private String baseUrl;
private String name;
}
This means that the configuration would be something like the following (in application.yml):
health-check:
apps:
- baseUrl: http://localhost:10000
name: app1
- baseUrl: http://localhost:10001
name: app2
Finally, you would need to create a bean for each app and register them in the application context:
#Slf4j
#Data
#Configuration
#ConfigurationProperties(prefix = "health-check")
public class AllAppPropertiesConfiguration {
private List<AppProperties> apps;
#Autowired
private GenericApplicationContext applicationContext;
#PostConstruct
fun init() {
for (AppProperties app : apps) {
applicationContext.registerBean(app.getName(), AppHealthCheckContributor.class, app);
}
}
}

Spring Boot - How to read properties from multiple custom yml

We are upgrading the spring boot version from 1.3.0.RELEASE to 2.3.12.RELEASE.
As per the old version, yml files were read using the following code snippet
#Configuration
#ConfigurationProperties(locations = "classpath:/config/myconf-source.yml")
public class MyConfigProperties {
private String configSource;
public String getConfigSource() {
return configSource;
}
public void setConfigSource(String configSource) {
this.configSource = configSource;
}
}
Config files in src/main/resources/config/
myconf-source.yml
news-source.yml
conf-mapping.yml
Content in myconf-source.yml
configSource: "TEST"
Corresponding Test Class
#ActiveProfiles("test")
#RunWith(SpringJUnit4ClassRunner.class)
#EnableAutoConfiguration
#SpringApplicationConfiguration(classes = SampleApplication.class)
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
#ConfigurationProperties(locations = "classpath:**/config/**")
public class MyConfigPropertiesTest {
#Autowired
private MyConfigProperties myConfigProperties;
#Test
public void testMyConfigProperties() {
String config = myConfigProperties.getConfigSource();
Assert.assertEquals(config, "TEST");
}
}
After changing to the new version, it throws an error Cannot resolve method 'locations'.
If I remove locations attribute how spring will know the class MyConfigProperties has to read myconf-source.yml
Also while running the test class, NullPointerException is thrown as myConfigProperties.getConfigSource(); becomes null.
I have tried various solutions posted but no luck,
Can anyone suggest how to make it work?
Thanks
#Configuration should be used if in that class you define beans with #Bean.
If not then remove it from there.
Also #Configuration does not make this class a bean to be autowired in the test that you require it to be.
If you want MyConfigProperties to be available for autowiring then you also need
#EnableConfigurationProperties(MyConfigProperties.class). This will make sure that this class is available as a spring bean in the application context.
So it would be
#PropertySource("classpath:/config/myconf-source.yml")
#ConfigurationProperties()
#EnableConfigurationProperties(MyConfigProperties.class)
public class MyConfigProperties {
private String configSource;
public String getConfigSource() {
return configSource;
}
public void setConfigSource(String configSource) {
this.configSource = configSource;
}
}
You can use #PropertySource annotation to read the yml file , you can read the below article :
https://www.baeldung.com/properties-with-spring

Spring Boot defining bean in configuration problem

I'm working on a project on my sparetime, on this project I have been asked to work with Spring Boot. I'm not familiar with Spring Boot from before, and I have tried searching here on stackoverflow and Google, but I can't seem to understand how the different solutions for my problem works.
First of all, this is my error message:
Parameter 0 of constructor in com.project.example.controller.VerverController required a bean of type 'com.project.example.dao.VerverDao' that could not be found.
Action:
Consider defining a bean of type 'com.project.example.dao.VerverDao' in your configuration.
After some reading, I understood that this could be because of Spring Boot don't scan this class / interface. So I tried to add ComponentScan, with no luck.
I tried to convert VerverDao from interface to class and register a bean with bean annotation. Still no luck. I tried to move the VerverDao from the dao package to the root package (the same as the Main class) and still no luck.
So if someone could please help me understand the error message, it would make my day.
This is my structure:
This is my code:
Probably some of the annotation is wrong as well, since I'm not used to Spring Boot, in advance thanks for correcting me on the use of these
It is also worth mentioning that I'm using Lombok as well on this project.
Main.java
#ComponentScan("com.gambinomafia")
#SpringBootApplication
public class Main extends SpringBootServletInitializer {
public static void main(String[] args) {
new Main().configure(new SpringApplicationBuilder(Main.class)).run(args);
}
}
Configuration.java
#Component
public class Configuration extends ResourceConfig {
public Configuration() {
register(VerverResourceImpl.class, VerverResource.class);
register(VerverController.class);
register(VerverDao.class);
}
}
VerverResource.java
#Resource
#Path("verver")
public interface VerverResource {
#GET
#Path("/{userid}")
#Produces(MediaType.APPLICATION_JSON)
String getRefferingUrl(#PathParam("userid") int userid);
}
VerverResourceImpl.java
#AllArgsConstructor(onConstructor = #__({#Inject}))
public class VerverResourceImpl implements VerverResource {
private VerverController controller;
#Override
public String getRefferingUrl(int userid) {
log.info("Collection reciever url for user id: {}", userid);
return controller.getRefferingUrl(userid);
}
}
VerverController.java
#Controller
#AllArgsConstructor
public class VerverController {
private VerverDao ververDao;
public String getRefferingUrl(int userid) {
User user = ververDao.getUsername(userid);
if (user.getUsername().isEmpty()) {
throw new NotFoundException("Did not find any user with id " + userid);
}
return "<url-to-site>/?verv=" + user.getUsername();
}
}
VerverDao.java
#AllArgsConstructor
public class VerverDao {
#Value("spring.datasource.url")
private String dbcon;
public User getUsername(int userid) {
Jdbi jdbi = Jdbi.create(dbcon);
return jdbi.withHandle(handle -> handle.createQuery(
"SELECT id, brukernavn FROM t_user WHERE id = :id")
.bind("id", userid)
.mapToBean(User.class)
.findOnly());
}
}
Feel free to ask for more information, if there is missing some information in the topic. I will try to answer as fast as possible.
First at all, you're setting a #ComponentScan with a wrong package in your Application class.
#ComponentScan("com.gambinomafia")
#SpringBootApplication
And, according to the picture, it has to be:
#ComponentScan("com.project.example")
#SpringBootApplication
EDIT: the use of #ComponentScan by #M.Deinum
Because of the Application Class is in the root package and #SpringBootApplication already contains #ComponentScan, It isn't needed to declare #ComponentScan.
Second, instead of use a DAO you can use Repositories. There is more information here: Spring Boot Repositories.
Third, to inject your objects you can use #Autowired over the constructor.
#Controller
public class VerverController
private VerverDao ververDao;
#Autowired
public VerverController(VerverDao ververDao) {
this.ververDao = ververDao;
}
}
There is more information here.
Log clearly says
VerverController required a bean of type 'com.project.example.dao.VerverDao' that could not be found.
It means there is no bean of type com.project.example.dao.VerverDao
Add #Repository on top of VerverDao to resolve the issue.
#Repository
#AllArgsConstructor
public class VerverDao {
}
Note: Ensure VerverDao is in component scanning path as per your package structure.

Spring Boot test with yaml properties by profile

So there's a lot of hits on this topic, but none of them have worked for me.
I have a very simple configuration class:
#Configuration
#ConfigurationProperties(prefix = "props")
public class TagIncluder {
private static final String PARAMETER_NAME = "tags";
private List<String> tags;
public TagIncluder() {
tags = new ArrayList<>();
}
public List<String> getTags() {
return tags;
}
#Handler
public void attachIncludedTags(Exchange exchange) {
exchange.getIn().setHeader(PARAMETER_NAME, tags);
}
}
I want this class to be able to load different property files. I am using yaml, and my file is named application-tag_test.yml. I have tried placing this file in src/main/resources, src/test/resources and src/test/resources/config, but it is never picked up.
This is the contents of the property file:
props:
tags:
- test
And finally, the test case:
#SpringBootTest
#ActiveProfiles("tag_test")
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TagIncluder.class)
public class TagIncluderTest extends ExchangeTestSupport {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
Exchange testExchange = createExchange();
sut.attachIncludedTags(testExchange);
Assertions.assertThat(testExchange.getIn().getHeader("tags", List.class))
.size().isGreaterThanOrEqualTo(1);
}
}
Additionally, I have tried placing an application.properties file in the above described locations with the following content:
spring.profiles.active=tag_test
What is required for Spring to set my yaml file as the desired configuration for my test class under test?
UPDATE
So after some exploration and trial and error, I have found that the following works:
#SpringBootTest
#ActiveProfiles("tag_test")
#RunWith(SpringJUnit4ClassRunner.class)
public class TagIncluderTest extends ExchangeTestSupport {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
Exchange testExchange = createExchange();
sut.attachIncludedTags(testExchange);
Assertions.assertThat(testExchange.getIn().getHeader("tags", List.class))
.size().isGreaterThanOrEqualTo(1);
}
}
The difference here is that I've removed the #ContextConfiguration annotation and I let Spring take care of all of that.
It is a lot slower, and I would prefer specifying what is needed. I think this might break in the future, for instance if I add another configuration class that will start with the entire context and throw errors because those properties are not included in my application-tag_test.yml configuration.
Finally, any of the above locations I tried for the configuration is valid with the above annotations. The application.properties to specify a profile is not needed.
If anyone knows a way to specify what should be loaded into the context instead, I'd be very grateful for another solution.
With some guidance of Jans suggestion above, I've managed to isolate the test to a slice. Auto configured testing is written about here, however that only touches on Springs predefined #..Test annotations.
If you dive deeper into the #WebMvcTest, for instance, you will find the #ImportAutoConfiguration annotation.
Using this, we can tell our test class to enable auto configuration for a single slice of our application. A tutorial is available here. The full list of factories available for auto configuration can be found in the spring-boot repository.
Finally, this is the entire test class:
#ActiveProfiles("tag_test")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = TagIncluder.class)
#ImportAutoConfiguration(classes = ConfigurationPropertiesAutoConfiguration.class)
public class TagIncluderTest extends ExchangeTestSupport {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
Exchange testExchange = createExchange();
sut.attachIncludedTags(testExchange);
Assertions.assertThat(testExchange.getIn().getHeader("tags", List.class))
.size().isGreaterThanOrEqualTo(1);
}
}
The class under test is untouched.
So now we can:
Use profiles
Use yaml
Test only our desired class in Spring Context
This has been very enlightening :)
The Spring Boot Test documentations states that
External properties, logging, and other features of Spring Boot are installed in the context by default only if you use SpringApplication to create it.
This means that you need to have a working Spring Boot Application in order to test anything related to property loading in a test case.
Also, setting a list from properties needs a setter. This works:
#Configuration
#ConfigurationProperties(prefix = "props")
public class TagIncluder {
private List<String> tags;
public void setTags(List<String> tags) {
this.tags = tags;
}
public List<String> getTags() {
return tags;
}
}
#Component
public class MyComponent {
#Autowired
TagIncluder tagIncluder;
}
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#SpringBootTest
#ActiveProfiles("tag_test")
#RunWith(SpringRunner.class)
public class TagIncluderTest {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
System.out.println(sut.getTags());
}
}

Spring boot #Autowired can't initialise class

I have following class as:-
#SpringBootApplication
#ComponentScan("com.ma.demospringboot")
public class DemoSpringBootApp {
public static void main(String[] args) {
SpringApplication.run(DemoSpringBootApp.class, args);
}
}
I have following class as:-
#Service
public class TopicService {
// if I comment out following autowired, then it is ok.
#Autowired
private TopicRepository topicRepository;
}
I have following interface as:-
public interface TopicRepository extends CrudRepository<Topic, String> {
}
I have following class as:-
#Entity
public class Topic {
#Id
private String id;
private String name;
private String description;
public Topic() {
}
}
I got following error when try to execute:-
APPLICATION FAILED TO START
Description:
Field topicRepository in com.ma.demospringboot.service.TopicService
required a bean of type
'com.ma.demospringboot.repository.TopicRepository' that could not be
found.
Action:
Consider defining a bean of type
'com.ma.demospringboot.repository.TopicRepository' in your
configuration.
Spring container is not finding your repository classes during the scan, add #EnableJpaRepositories to explicitly specify the packages of where your repository classes exist, as shown below:
#SpringBootApplication
#ComponentScan("com.ma.demospringboot")
#EnableJpaRepositories("com.ma.demospringboot.repository")
#EntityScan(basePackages = "com.ma.demospringboot.domain")
public class DemoSpringBootApp {
//your current code here
}
UPDATE1:
Not a managed type: class com.ma.demospringboot.domain.Topic
Now, your Topic entity class not found, so you need to #EntityScan(basePackages = "com.ma.demospringboot.domain") to scan the entity classes (as show above).
UPDATE2:
I did exactly as you suggested, but NOT working
There is a problem with the way that you are packaging the classes, double check on that, also ensure that the latest classes have been compiled/built & used by the server.
Your Spring container is unable to find and identify your JPA repositories.
Add #EnableJpaRepositories("com.ma.demospringboot.repository") on your DemoSpringBootApp class
#SpringBootApplication
#ComponentScan("com.ma.demospringboot")
#EnableJpaRepositories("com.ma.demospringboot.repository")
public class DemoSpringBootApp {
public static void main(String[] args) {
SpringApplication.run(DemoSpringBootApp.class, args);
}
}
add following code in your ApplicationContext.xml
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />

Categories