#ConfigurationProperties, #Value not Working YET Passing the Tests - spring-boot

I have a strange problem reading configuration, none of solutions I've seen seem to work. Here is my code:
#SpringBootApplication
#EnableConfigurationProperties
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Here is my properties class
#Component
#ConfigurationProperties(prefix = "my")
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class MyProperties {
private String host;
private int port;
}
I then use MyProperties class in my class using #Autowired:
#Autowired
private MyProperties props;
However, I'm getting null for my props object.
Strangely, this is passing the tests just perfectly:
#SpringBootTest
class ApplicationTests {
#Autowired
private MyProperties props;
#Test
void test_configuration() {
Assertions.assertEquals(props.getHost(), "xx.xx.xx.xx");//pass!
Assertions.assertEquals(props.getPort(), xxxxxx);//pass!
}
}
It has totally refused to work, and so has #Value injection. What could I be missing?
EDIT
Here's complete code of how I'm using #Autowired on MyProperties (I've included #Value which is also not working)
#Slf4j
#Component //also tried #Configurable, #Service
public class MyService {
#Autowired
private MyProperties props;
#Value("localhost")
public String host;
public void post() {
log.info(host + props);// =null and null
}
}
EDIT2
However, I've noticed that on the controller, it works perfectly okay:
#Slf4j
#RestController
#Service
public class Main {
#Autowired
private MyProperties props;
#Value("localhost")
private String host;
#GetMapping("/post")
public void post() {
log.info(host + props);//=it's perfect!
new MyService().post();// calling MyService - where #Autowired or #Value is failing
}
}

The reason this isn't working is because the MyService you're using isn't a Spring bean, but an instance you created by yourself (using new MyService()).
To make this work, you should autowire MyService, in stead of creating your own instance:
#Slf4j
#RestController
public class Main {
#Autowired // Autowire MyService
private MyService myService;
#GetMapping("/post")
public void post() {
myService.post(); // Use the myService field
}
}
For more information, look at this Q&A: Why is my Spring #Autowired field null.

UPDATE:
new MyService() is not a "spring bean", thus can't be auto-wired with anything!;)
1. Lombok
Some people use Project Lombok to add getters and setters automatically. Make sure that Lombok does not generate any particular constructor for such a type, as it is used automatically by the container to instantiate the object.
With "such a type" ConfigurationProperties is referred in Externalized Configuration (one of my favorite chapters;) More Exact: 2.8.1. JavaBean properties binding, at the bottom of second "Note!" ;)
So this could be a reason (for strange behavior).

Related

How to use #TestConfiguration

How to override #Configuation which is present under src/main/java with #TestConfiguration during unit tests?
#Configuration
public class AppConfig {
#Bean
public EmployeeService employeeService(){
return new EmployeeService();
}
}
#Component
public class ServerStartSetup implements CommandLineRunner {
#Autowired
private EmployeeService employeeService;
public void run(String... args) {
// do something with employee service
}
}
I would like to override the above bean with some below custom bean for testing purposes.
#TestConfiguration
public class TestAppConfig {
#Bean
public EmployeeService employeeService(){
return new FakeEmployeeService();
}
}
#SpringBootTest
#Import(TestAppConfig.class)
public class UnitTest {
}
However AppConfig does not seem to be skipped. That is , it throws an error saying that there is a bean with same name employeeService. If I rename bean method name in the TestAppConfig, it injects the bean created via AppConfig.
How to fix this.?
Note: One possible solution is using #Profile. I am looking for anything other than using Profiles.
I tested locally and found that changing the method name or #Bean to #Bean("fakeEmployeeService") and adding the #Primary annotation works.
#SpringBootTest
class DemoApplicationTests {
#Autowired
private EmployeeService employeeService;
#TestConfiguration
static class TestConfig {
//#Bean("fakeEmployeeService")
#Bean
#Primary
public EmployeeService employeeServiceTest() {
return new EmployeeService() {
#Override
public void doSomething() {
System.out.println("Do something from test...");
}
};
}
}
...
}
If we want to override a bean definition in #TestConfiguration, we need:
To use the same name as the overridden bean. (Otherwise it would be an "additional" bean and we could get conflict/'d have to qualify/primary)
Since spring-boot:2.1: spring.main.allow-bean-definition-overriding=true (set this in tests ONLY!plz!)
#ref
Then, with:
#TestConfiguration
public class TestAppConfig {
#Bean // when same name, no #Primary needed
public EmployeeService employeeService(){ // same name as main bean!
return new FakeEmployeeService();
}
}
We can do that:
#Import(TestAppConfig.class)
#SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
public class UnitTest {
... // EmployeeService will be "fake", the rest is from "main config"
You can mock the AppConfig bean in your test like this:
#MockBean
private AppConfig config;
Or, like you said, just use profiles.

How to #Autwired MessageSource in spring into Entity class correctly?

I have the following entity in spring boot application:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Audited
#XmlRootElement
#XmlAccessorType(XmlAccessType.NONE)
#Table(name = "currency", catalog = "currency_db")
public class Currency implements java.io.Serializable {
#Autowired
Messages messages;
As for message, it just a container of spring MessageSource here it is:
#ApplicationScope
#Component
#Slf4j
public class Messages {
#Autowired
private MessageSource messageSource;
private MessageSourceAccessor accessor;
#PostConstruct
private void init() {
accessor = new MessageSourceAccessor(messageSource, Locale.ENGLISH);
log.info("Messages initialized");
}
public String get(String code) {
return accessor.getMessage(code);
}
}
I'm getting the following error when run mvn clean install. Any idea what I'm missing here?
org.hibernate.MappingException: Could not determine type for: com.company.currencyservice.Messages, at table: currency, for columns: [org.hibernate.mapping.Column(messages)]
It's looks like hibernate think it's a column. Thanks.
Entities are not Spring beans and therefor you cannot use dependency injection in entities.
If you want to access a Spring bean from within an entity you can use a helper class like this:
#Service
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static <T> T bean(Class<T> beanType) {
return context.getBean(beanType);
}
public static Object bean(String name) {
return context.getBean(name);
}
#Override
public void setApplicationContext(#SuppressWarnings("NullableProblems") ApplicationContext ac) {
context = ac;
}
}
Then you can use ApplicationContextProvider.getBean(Messages.class) to get access to the Messages.

Spring boot create multiple mocks of the same interface doesn't work with constructor

I try to implement MockitoExtension in Spring Boot with a simple service class. When I implement it via field-set, it works. But when I implement it constructor, i doesn't work.
ExampleService:
#Service
public class ExampleService
{
private final AmqpTemplate requestTemplate;
private final AmqpTemplate resultTemplate;
public ExampleService(#Qualifier("requestTemplate") AmqpTemplate requestTemplate,
#Qualifier("resultTemplate") AmqpTemplate resultTemplate)
{
this.requestTemplate = requestTemplate;
this.resultTemplate = resultTemplate;
}
}
ExampleServiceTest:
#ExtendWith(MockitoExtension.class)
public class ExampleServiceTest
{
#InjectMocks
private ExampleService exampleService;
#Mock(name = "requestTemplate")
private AmqpTemplate requestTemplate;
#Mock(name = "resultTemplate")
private AmqpTemplate resultTemplate;
#Test
public void test1()
{
...
}
}
requestTemplate and resultTemplate looks like same object on test cases and it behaves wrong...
When I implement it via Autowired, it works...
#Service
public class ExampleService
{
#Autowired
private AmqpTemplate requestTemplate;
#Autowired
private AmqpTemplate resultTemplate;
...
}
I wonder the constructor solution and reason why it doesn't work.

Spring Boot and/or Vaadin autowiring behaviors I do not understand

I am working on a Spring Boot application an am having issues where items are not being auto-wired as I expect. What is adding to this confusion is that I have another Spring Boot application that also happens to involve Vaadin. In this application, auto-wiring seems more "automated".
Here is what I mean. In the Vaadin app, a #Service object, is injected into a Vaadin "view" with little effort. Additionally, the service object itself is injected with a Repository object. I need to do almost nothing to make this all happen.
In my other non-Vaadin application, I set up the code to do similar things, but in this case, it does not work. The application fails to start and complains as below:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.mycompany.app.backend.service.TaskMasterService required a bean of type 'com.mycompany.app.backend.repository.TaskMasterRepository' that could not be found.
Action:
Consider defining a bean of type 'com.mycompany.app.backend.repository.TaskMasterRepository' in your configuration.
I assume that I'm missing some configuration somewhere, or that the Vaadin application has "extra" functionality added that makes this work.
Here are snippets of the Vaadin application:
TasksView.java:
#Route("main")
public class TasksView extends VerticalLayout {
private final TaskService taskService;
private final TaskNoteService noteService;
public TasksView(TaskService taskService, TaskNoteService noteService) {
this.taskService = taskService;
this.noteService = noteService;
}
...
}
TaskService.java:
#Service
#Transactional
public class TaskService {
private TaskRepository taskRepo;
public TaskService(TaskRepository taskRepo) {
this.taskRepo = taskRepo;
}
...
}
TaskRepository.java:
public interface TaskRepository extends JpaRepository<Task, Long>, TaskRepositoryCustom {
...
}
I don't need to #Autowired anything or provide a #Configuration class or define a #Bean anywhere. It just works.
Here are some snippets from the non-Vaadin application:
IngesterRunner.java:
#Service
public class IngesterRunner {
private TaskMasterService tmService;
private TaskRelationshipService trService;
private VariantDetailService vdService;
public IngesterRunner(TaskMasterService tmService, TaskRelationshipService trService,
VariantDetailService vdService) {
this.tmService = tmService;
this.trService = trService;
this.vdService = vdService;
}
...
}
TaskMasterService.java:
#Service
#Transactional
public class TaskMasterService {
private final TaskMasterRepository repo;
public TaskMasterService(TaskMasterRepository repo) {
this.repo = repo;
}
...
}
TaskMasterRepository.java:
package com.mycompany.app.backend.repository;
public interface TaskMasterRepository extends JpaRepository<TaskMaster, Long> {
}
JPAConfiguration.java:
#Configuration
#EnableJpaRepositories(basePackages = "com.mycompany.app.backend.repository", transactionManagerRef = "jpaTransactionManager")
#EnableTransactionManagement
public class JPAConfiguration {
private static final String[] ENTITY_PACKAGES_TO_SCAN = {
"com.mycompany.app.backend.entity" };
...
}
I don't understand what is different about the first application that makes auto-wiring work so much more smoothly. Is it something I have in my pom.xml? Is it some sort of extra support provided by Vaadin?
If requested, I can try to include the POM files if that will help.
Update:
I have tried a number of suggestions, but none seem to have any effect. I added #Autowired annotations, but it makes no difference.
#Service
public class IngesterRunner {
private TaskMasterService tmService;
private TaskRelationshipService trService;
private VariantDetailService vdService;
public IngesterRunner(#Autowired TaskMasterService tmService,
#Autowired TaskRelationshipService trService, #Autowired VariantDetailService vdService) {
this.tmService = tmService;
this.trService = trService;
this.vdService = vdService;
}
...
}
#Service
#Transactional
public class TaskMasterService {
private final TaskMasterRepository repo;
public TaskMasterService(#Autowired TaskMasterRepository repo) {
this.repo = repo;
}
...
}
#Repository annotation in TaskMasterRepository.java, like
#Repository
public interface TaskMasterRepository extends JpaRepository<TaskMaster, Long> {
}

How to load springboot properties?

I'm trying to load properties once in my springboot application.
Actually, I have created a class to do that :
#Configuration
#PropertySource(value = { "classpath:parameters.properties", "classpath:iot.properties" })
public class PropertiesHelper {
#Autowired
protected Environment env;
private static Environment properties;
#PostConstruct
public void init() {
properties = env;
}
public static String getProperty(final String propertyName) {
return properties.getProperty(propertyName);
}
}
This class work fine but this is not clean code (sonar hate my static variable).
So, how can I load in my springboot application all my properties correctly and only once ?
#PropertySource("classpath:parameters.properties")
#PropertySource("classpath:iot.properties")
public class PropertiesHelper {
#Value( "${Value.you.need}" )
private String valueYouNeed;
}
parameters.properties
Value.you.need=12345
Make it something like this, it should be work in your scenario.
An elegant solution
If you just want to eliminate the sonar alarm(sonar hate my static variable)
// application.properties
spring.profiles.active=test1010
// IOC config bean container
#Configuration
#Data
public class PropertiesConfig{
#Value("${spring.profiles.active:preline}")
private String profiles;
}
//Use
#Autowired
private PropertiesConfig propertiesConfig;
#GetMapping("/getPropertiesConfig")
public String getPropertiesConfig(){
return propertiesConfig.getProfiles();
}
I think the above scheme is a more elegant way~,Do you have a better solution?

Resources