Spring Boot Failed to use #Value for injection - java

Spring Boot version <version>2.2.0.RELEASE</version>
Error goes as follows:
Description:
Parameter 2 of constructor in
com.shawn.foodrating.service.impl.AdServiceImpl required a bean of
type java.lang.Integer that could not be found.
Action:
Consider defining a bean of type 'java.lang.Integer' in your configuration.
My Code:
#Service
#Transactional(rollbackOn = Exception.class)
#AllArgsConstructor
public class AdServiceImpl implements AdService {
private AdRepository repository;
private FileService fileService;
#Value("${app.ad.DefaultPageSize}")
private Integer DEFAULT_PageSize;
#Value("${app.ad.ImagePath}")
private String AD_IMAGE_PATH;
#Value("${app.ad.ImageUrl}")
private String AD_IMAGE_URL;
Load property file
#SpringBootApplication
#PropertySource("classpath:app.properties")
public class FoodRatingApplication {
public static void main(String[] args) {
SpringApplication.run(FoodRatingApplication.class, args);
}
}
Not Sure what is wrong with it.

When you use Lombok's #AllArgsConstructor it must create a constructor for all your fields, those annotated with #Value and those that aren't.
Now Lombok doesn't even know anything about #Value annotation of spring. So the generated constructor looks something like this:
public AdServiceImpl(AdRepository repository, FileService fileService, Integer DEFAULT_PageSize, String AD_IMAGE_PATH, String AD_IMAGE_URL) {
this.repository = repository;
....
}
You can run Delombok to see the actually generated code.
Spring on the other hand when sees a single constuctor tries to call it to create the bean (AdServiceImpl) in this case, and only after that iterates through its fields and inject data annotated by #Value.
Now, when spring calls the constructor, it sees an integer (DEFAULT_PageSize), has no clue that its a value (and spring has to inject something brcause its a constructor injection), and throws an Exception.
So in terms of resolution:
Don't use all args constructor of lombok in this case and instead create a non-lombok constructor for AdRepository and FileService only)
Alternatively create a constructor with #Value annotated parameters instead of field injection (remove #Value on fields):
public AdServiceImpl(AdRepository repository, FileService fileService, #Value(${app.ad.DefaultPageSize}"} Integer DEFAULT_PageSize, #Value(...) String AD_IMAGE_PATH, #Value(...) String AD_IMAGE_URL) {
this.repository = repository;
....
}

The root cause is that you are using Lombok #AllArgsConstructor while some of the properties are populated by #Value(..).
Replace the #AllArgsConstructor with #RequiredArgsConstructor and give it a try.
Note: Adding this comment for future references.

Add to projeck lombok.config file with this lines:
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value

Related

Injecting spring beans in non managed objects

the below bean of Class I want to inject in other non-managed bean, but it is not working as expected
#Component
#Setter
#Getter
public class AbstractLayoutProperties {
#Value("${spring.application.name}")
private String appName;
#Autowired
S3Service s3Service;
#Autowired
S3Client s3Client;
}
Below is the class which is not managed by the spring, but I am using #Configurable
#Configurable(preConstruction = true, autowire = Autowire.BY_NAME)
#EnableSpringConfigured
public class OverlayServiceImpl
implements GenericVehicleOverlayService<T, R> {
public findOnlyActive(){
appName = layoutProperties.getAppName(); // throwint NullPointerException beacuse the object not injected properly
}
#Autowired
AbstractLayoutProperties layoutProperties;
}
One more point, findOnlyActive method I am not calling directly, I am calling this from another service, lets say
#Service
public class OtherService{
public void findActive(){
OverlayServiceImpl impl=new OverlayServiceImpl();
impl.findOnlyActive();
}
#Autowired
OtherRepository otherRepo;
}
Problem statrement:
when impl.findOnlyActive(); is executed, it should inject all the required beans inside OverlayServiceImpl. In the same class I have two beans which are autowired, it seems none of them injected, this is the reaosn that every time I am encountering Nullpointer exception. so my question is how do I make it work, what are the steps and action I need to take so that spring will inject the dependencies in non managed object i,e OverlayServiceImpl.

Spring bean not being picked up?

I have a java spring project with the below service:
#Slf4j
public class DialogFlowService {
private String projectId;
private String sessionId;
private String languageCode;
public DialogFlowService(DialogFlowConfig dialogFlowConfig) {
log.info("aaa" + dialogFlowConfig.languageCode);
this.projectId = dialogFlowConfig.projectId;
this.sessionId = dialogFlowConfig.sessionId;
this.languageCode = dialogFlowConfig.languageCode;
}
}
The constructor takes the below class as an argument:
#Configuration
#ConfigurationProperties(prefix = "dialog-flow")
public class DialogFlowConfig {
#NotNull
public String projectId;
#NotNull
public String sessionId;
#NotNull
public String languageCode;
}
In theory, this should be instantiated by the below bean:
#Bean
public DialogFlowService dialogFlowService() {
return new DialogFlowService(new DialogFlowConfig());
}
However in practice, when I try to log one of the constructor arguments, it comes up as null. Am I missing something?
I think changing your third code snippet like this would do the trick.
#Bean
public DialogFlowService dialogFlowService(DialogFlowConfig dialogFlowConfig) {
return new DialogFlowService(dialogFlowConfig);
}
The DialogFlowConfig class is already marked as #Configuration. Hence it is managed by the Spring Application context. So you dont have to explicitly make an object using the new keyword. You can just take it as a parameter
Try putting #EnableConfigurationProperties(DialogFlowConfig.class) into you Spring Application class.
This is similar to what's being described here: Why is my Spring #Autowired field null?
Essentially, by instantiating the DialogFlowConfig instance yourself and not handing it over to Spring, you're preventing Spring from post processing it and injecting ConfigurationProperties property values.
Instead create a #Bean method for DialogFlowConfig and use the corresponding Spring bean to create your DialogFlowService. For example
#Bean
public DialogFlowService dialogFlowService(DialogFlowConfig dialogFlowConfig) {
return new DialogFlowService(dialogFlowConfig);
}
#Bean
public DialogFlowConfig dialogFlowConfig() {
return new DialogFlowConfig();
}
Spring will use the #Bean annotated dialogFlowConfig() factory bean method to instantiate and process the corresponding instance (setting its fields). It'll then use it with the dialogFlowService() factory method.
Note: If you do it this way, you'll need to remove #Configuration annotation from DialogFlowConfig, assuming you were previously component scanning it. Alternatively, if you were correctly component scanning, you don't even need the additional #Bean annotated dialogFlowConfig() factory method I proposed. Just inject the DialogFlowConfig bean declared by its #Configuration annotation in the dialogFlowService method.
First of all you need to use either setter getter in DialogFlowConfig or #Value annotation on all properties. You also need to annotate your service class DialogFlowService with #Service stereotype

#TestPropertySource - Values not set / set as null from test properties file

My Junit is not picking up properties set in test properties file.
I get no error, but value returned from properties file is null
CLASS TO BE TESTED:
package com.abc.mysource.mypackage;
#Component
#ComponentScan
public class MyHelper {
#Autowired
#Qualifier("commonProperties")
private CommonProperties commonProperties;
public LocalDateTime method1ThatUsesCommonProperties(LocalDateTime startDateTime) throws Exception {
String customUserType = commonProperties.getUserType(); // Returns null if run as JUnit test
//Further processing
}
}
SUPPORTING COMPONENTS - BEANS & CONFIG CLASSES:
package com.abc.mysource.mypackage;
#Component
public class CommonProperties {
#Value("${myhelper.userType}")
private String userType;
public String getUserType() {
return userType;
}
public void setCalendarType(String userType) {
this.userType = userType;
}
}
CONFIG CLASS:
package com.abc.mysource.mypackage;
#Configuration
#ComponentScan(basePackages ="com.abc.mysource.mypackage.*")
#PropertySource("classpath:default.properties")
public class CommonConfig {}
default.properties under src/main/resources
myhelper.userType=PRIORITY
MY TEST CLASS:
package com.abc.mysource.mypackage.test;
#RunWith(SpringRunner.class)
#ContextConfiguration(classes=MyHelper.class)
#TestPropertySource("classpath:default-test.properties")
#EnableConfigurationProperties
public class MyHelperTest {
#MockBean(name="commonProperties")
private CommonProperties commonProperties;
#Autowired
private MyHelper myHelper;
#Test
public void testMethod1ThatUsesCommonProperties() {
myHelper.method1ThatUsesCommonProperties();
}
}
default-test.properties defined under /src/test/resources:
myhelper.userType=COMMON
NOTE:
I moved default-test.properties to /src/main/resources - commonProperties.getUserType() is null
I even used #TestPropertySource(properties = {"myhelper.userType=COMMON"}). Same result
NOTE 2:
I tried the solution on #TestPropertySource is not loading properties.
This solution requires me to create a duplicate bean called CommonProperties under src/test/java. But #MockBean fails when I do
#MockBean(name="commonProperties")
private CommonProperties commonProperties;
Please do not mark a duplicate.
NOTE 3:
Mine is a spring, not a spring boot application.
MockBeans are suited if you don't need specific state. Usually this bean is "isolated" and every method call of this bean will have the same result. It is "isolated" -> the service that uses #Value annotation will not apply on this bean.
What you need is a "normal" bean, properly constructed and initialized. Please use #Autowired annotation and define another bean if needed, using a test profile.

Spring inject without autowire annotation

I find some answer: https://stackoverflow.com/a/21218921/2754014 about Dependency Injection. There isn't any annotation like #Autowired, #Inject or #Resource. let's assume that there isn't any XML configuration for this example TwoInjectionStyles bean (except simple <context:component-scan base-package="com.example" />.
Is it correct to inject without specify annotation?
From Spring 4.3 annotations are not required for constructor injection.
public class MovieRecommender {
private CustomerPreferenceDao customerPreferenceDao;
private MovieCatalog movieCatalog;
//#Autowired - no longer necessary
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
#Autowired
public setMovieCatalog(MovieCatalog movieCatalog) {
this.movieCatalog = movieCatalog;
}
}
But you still need #Autowired for setter injection. I checked a moment ago with Spring Boot 1.5.7 (using Spring 4.3.11) and when I removed #Autowired then bean was not injected.
Yes, example is correct (starting from Spring 4.3 release). According to the documentation (this for ex), if a bean has single constructor, #Autowired annotation can be omitted.
But there are several nuances:
1. When single constructor is present and setter is marked with #Autowired annotation, than both constructor & setter injection will be performed one after another:
#Component
public class TwoInjectionStyles {
private Foo foo;
public TwoInjectionStyles(Foo f) {
this.foo = f; //Called firstly
}
#Autowired
public void setFoo(Foo f) {
this.foo = f; //Called secondly
}
}
2. At the other hand, if there is no #Autowire at all (as in your example), than f object will be injected once via constructor, and setter can be used in it's common way without any injections.

Trying to inject an object into JUnit test

I have had to adapt a project I've been working on to work differently, using an injected object (documentDao) to access the methods for adding/updating/etc. records in a database. Where necessary I simply injected this object into the constructor, but of course this won't work with JUnit tests (which can only have no-argument constructors), so I'm stuck on how to get the object into the test class.
The first code snippet shows a dumbed-down version of one of the test classes. The problem is that I need to create the documentDao object so I can pass it as an argument into the BackendApiController instantiation statement.
The second snippet is the first part of the DocumentDaoImpl class, which needs to be injected.
Any suggestions would be welcomed.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class ApiBackendTests {
#Configuration
#PropertySource(value = "classpath:system.properties")
static class ContextConfiguration {
}
private static BackendApiController backendApiController = new BackendApiController(documentDao);
#Test
public void retrieveSampleStatementList() {
String response = backendApiController.genericStatementList(x,y,z);
String eStatementId = "";
if (response.indexOf("_id") > 0) {
eStatementId = response.substring(response.indexOf("<_id>") + 5, response.indexOf("</_id>"));
}
// if this test is true, then at least one statement document was found in the above search.
assertTrue(response.indexOf("_id") > 0);
}
}
#Repository
public class DocumentDaoImpl<T> implements DocumentDao<T> {
public DocumentDaoImpl() {
}
#Inject
DBCollection dbCollection;
#Inject
GridFS gridFS;
#Autowired
ObjectMapper objectMapper;
#Override
public String insert(CommonDocument document) {
There's still not enough information to say anything for sure, but I believe you can try using #Autowired to wire up your needed component:
#Autowired
private DocumentDao documentDao;
You got the error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.roler.res.test.ApiBackendTests': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.roler.res.mongodb.dao.DocumentDao
That means Spring isn't aware of the DocumentDao bean yet. There are several ways to do that, but I think the easiest way is putting this in your configuration context:
<context:component-scan base-package="package.contain.your.dao"/>
It will tell Spring to scan the package in search for components with annotation.
UPDATE: since you don't use XML configuration, #ComponentScan is the way to go

Categories