If I wanted to use the annotation #Qualifier on a constructor dependency injection, I would have something like the following:
public class Example {
private final ComponentExample component;
#Autowired
public Example(#Qualifier("someComponent") ComponentExample component) {
this.component = component;
}
}
I know Lombok's annotations to reduce boilerplate code and don't have to include a constructor would be as follows: #RequiredArgsConstructors(onConstructor=#__(#Inject)) but this only works with properties without qualifiers.
Anyone know if it is possible to add qualifiers in #RequiredArgsConstructor(onConstructor = #__(#Autowired))?
EDIT:
It is FINALLY POSSIBLE to do so! You can have a service defined like this:
#Service
#RequiredArgsConstructor
public class SomeRouterService {
#NonNull private final DispatcherService dispatcherService;
#Qualifier("someDestination1") #NonNull private final SomeDestination someDestination1;
#Qualifier("someDestination2") #NonNull private final SomeDestination someDestination2;
public void onMessage(Message message) {
//..some code to route stuff based on something to either destination1 or destination2
}
}
Provided that you have a lombok.config file like this in the root of the project:
# Copy the Qualifier annotation from the instance variables to the constructor
# see https://github.com/rzwitserloot/lombok/issues/745
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier
This was recently introduced in latest lombok 1.18.4, I wrote about it in my blogpost, and I am proud to say I was one of the main driving forces pushing for the implementation of the feature.
The blog post where the issue is discussed in detail
The original issue on github
And a small github project to see it in action
You may use spring trick to qualify field by naming it with desired qualifier without #Qualifier annotation.
#RequiredArgsConstructor
public class ValidationController {
//#Qualifier("xmlFormValidator")
private final Validator xmlFormValidator;
I haven't test whether the accepted answer works well, but instead of create or edit lombok's config file, I think the cleaner way is rename the member variable to which name you want to qualifier.
// Error code without edit lombok config
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class Foo {
#Qualifier("anotherDao") UserDao userDao;
}
Just remove #Qualifier and change your variable's name
// Works well
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class Foo {
UserDao anotherDao;
}
Related
I'm searching for a elegant way, to define fields in standard spring-service.
Without lombok our service looks like this:
#Service
public class ServiceA {
private final String applicationName;
private final SpecialHandler specialHandler;
private final ServiceC serviceC;
public ServiceA(final ConfigBean config,
final ServiceB serviceB,
final ServiceC serviceC) {
this.applicationName = config.getBaseConfig().getApplicationInfo().getName();
this.specialHander = serviceB.getSpecialForAppName(this.applicationName);
// PROBLEM: each direct dependency forces us to write more and more manual code
this.serviceC = serviceC;
}
}
Now, our team want to use the lombok-constructor only (so we can easily add other services). The service above will now look this:
#Service
#RequiredArgsConstructor
public class ServiceA {
private final ServiceC service;
// ^- with lombok, this is very pretty and simpel
private final ConfigBean config;
private final SpecialHandler specialHandler;
// ^- PROBLEM: these fields only used in the "createFields()"-method
// can we inline them somehow?
private String applicationName;
private SpecialHandler specialHanlder;
// ^- PROBLEM: these fields are not final anymore
// can we "make fields final again"?
#PostConstruct
public void createFields() { // maybe we can put parameters to the post-construct?
this.applicationName = this.config.getBaseConfig().getApplicationInfo().getName();
this.specialHander = this.serviceB.getSpecialForAppName(this.applicationName);
}
}
Question
How can I solve the issues (mentioned in the sourceCode-comments)?
Footnote
I saw this "problem" in many projects. The variants, which I mentioned above, are the only solutions I saw yet. Both solutions getting more ugly when raising the number of fields.
Lombok only writes boilerplate code for you. It means that, as soon as you have anything non trivial to do, you can't use it anymore.
So if you want to keep your fields computed in the constructor final, you will have to write the constructor by hand.
As far as I know, a #PostConstruct method can't accept any parameter either.
There are two possibles tracks to follow:
you can certainly use field-based or setter-based injection, using #Autowired annotation.
Pass an already constructed SpecialHandler to your service. You can do so, , rather than using #Service annotation, by creating it in a #Bean method inside a #Configuration class for example.
I'm trying to achieve something like this:
#Controller
public SomeController {
#CustomConfig("var.a")
private String varA;
#CustomConfig("var.b")
private String varB;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String get() {
return varA;
}
}
CustomConfig would be an #Interface class that accepts one value parameter. The reason why we are not using #Value is because this will not come from config file but from API (such as https://getconfig.com/get?key=var.a). So we are going to make HTTP request to inject it.
So far I've only manage to make something work if the varA and varB is inside get() method as parameter, by using below in a class that extends WebMvcConfigurerAdapter:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
CustomConfigResolver resolver = new CustomConfigResolver();
argumentResolvers.add(resolver);
}
And inside CustomComfigResolver.resolveArgument() we would do the HTTP query, but that's not really what we wanted, we need it to be injected as class variable.
Does anyone have experience in resolving it at class variable level?
Thank you
This could work if you use #Value instead of your own custom annotation. This uses the built in environment:
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
public class TcpIpPropertySourceConfig implements InitializingBean {
#Autowired
private ConfigurableEnvironment env;
#Autowired
private RestTemplate rest;
public void afterPropertiesSet() {
// Call your api using Resttemplate
RemoteProperties props = //Rest Call here;
// Add your source to the environment.
MutablePropertySources sources = env.getPropertySources();
sources.addFirst(new PropertiesPropertySource("customSourceName", props)
}
}
What you are trying to achieve is difficult when you start to consider "unhappy" scenarios. Server down / not reachable. You need to account for all of that in the method above.
I would highly recommend to instead use Spring Cloud Config. Great guide on that is here: https://www.baeldung.com/spring-cloud-configuration
This provides:
- Reloading of your #Value() properties, so no custom annotation needed.
- A more stable server and great Spring integration out of the box.
Best of all, it is easy to apply Retries and Backoffs if the configuration server goes down (see https://stackoverflow.com/a/44203216/2082699). This will make sure your app doesn't just crash when the server is not available.
I've got several #Configuration classes which do specify custom #ConfigurationProperties("sample") and are used within to instantiate several beans that are going to be used my business logic classes later on.
However, I've been trying to do this approach with an inner #Component class so I don't need to fit that within an existing specific or generic config and see what happens.
#Component
#ConfigurationProperties("myclass")
public class MyClass {
private String attribute;
(Constructor, getters and setters for attribute and other methods...)
}
And within my application.properties file I do specify that attribute value as myclass.attribute=value.
Doing it this way results in a null value everytime. Do #Component accept reading .properties file or should it still be in a #Configuration class?
I should have put it as a comment. But didn't want someone to miss this trivial thing.
Okay the issue is - You are missing the '$' (DOLLAR SYMBOL). Wondering why nobody noticed it?
In your properties file if you have this :
myclass.attribute=value
Then to access it in any class, do this:
#Value("${myclass.attribute}")
Noticed the $ symbol above??
Everything is working as expected, even using #ConfigurationProperties in class annotated with #Component. Please try :
application.properties :
myclass.attribute=value
MyClass class :
#Data
#Component
#ConfigurationProperties("myclass")
public class MyClass {
private String attribute;
}
Test class :
#RunWith(SpringRunner.class)
#SpringBootTest
public class FooTests {
#Autowired
private MyClass myClass;
#Test
public void test() {
System.out.println(myClass.getAttribute());
}
}
You do need the #EnableConfigurationProperties annotation on on of you configuration classes, eg the application class.
#SpringBootApplication
#EnableConfigurationProperties
public class MySpringBootApp {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApp.class);
}
}
I've never used the #ConfigurationProperties annotation but if you want to set your attribute from a value in your application.properties I'll recommend using the #Value annotation :
application.properties :
myclass.attribute=foo
#Component
public class MyClass {
#Value("myclass.attribute")
private String attribute;
// ...
}
This way every instance of MyClass will have attribute with the default value foo
I've got a problem with #Autowiring being null. I am looking for advice how to model it the spring-boot-way.
My Soap-Services get really big using lots of Repository classes. This gives me a large list of #Autowired already. Now when I want to call a helper-class like HeaderValidator.class I can't instantiate and call it like a POJO. This because everything annotated #Autowiring in my HeaderValidator is null. I can make it work when I add #Autowired at line (1) and remove the content of (2) in SoapServiceImpl.
But this will end in a huge list of #Autowired annotated fields and this looks ugly. I want to prevent this even it works for now.
This Article mentions the #Configurable with AspectJ. But the Article is from 2013 and Spring-Boot has developed since. I tried the #Configurable solution but it didn't work in my case.
How can I inform my SpringBoot-Application of a class copy? Is the #Configurable-way still the only one? Or did I simply model the application wrong?
Application.class:
#SpringBootApplication
public class Application {
private static ApplicationContext ctx;
public static void main(String... args) {
ctx = SpringApplication.run(Application.class, args);
publishSoapServices();
}
SoapService.class (gets published when calling publishSoapServices() in Application.class):
public class SoapServiceImpl implements SoapService {
#Autowired
ProjectRepository projectRepo;
(1) "#Autowired"
HeaderValidator headerValidator;
#Override
public EventReport send(#WebParam(name = "header") HeaderType headerType,
#WebParam(name = "content") ContentType contentType) {
return storeServiceData(headerType, messageType);
}
private EventReport storeServiceData(HeaderType headerType, ContentType contentType) {
projectRepo.save(contentType);
(2) "HeaderValidator headerValidator= new HeaderValidator()"
return headerValidator.validate(headerType);
}
My problem class:
#Service
public class HeaderValidator {
#Autowired
ValidFieldsRepository validFieldsRepo; //<-- always null!
I managed to solve my problem. It was simply due to bad design. I went trough the application and configured #Configurableit correctly. Now it works all fine. Thanks to M.Deinum!
I would like to inject a FileInputStream as a constructor argument using spring annotation.
Lets say I have the following class (before doing constructor arg injection)
#Component
public class MyClass{
private BlaClass xy;
public MyClass(InputStream is)
{
this.xy = new BlaClass(is);
}
}
So now my question is if I can use the #Value annotation or a similar one in order to inject the input stream? Should be something like this:
#Component
public class MyClass{
private BlaClassTakingAnInputStream xy;
//this is of course not correct
public MyClass(#Value("classpath:path/to/a/file") is)
{
this.xy = new BlaClassTakingAnInputStream(is);
}
}
p.s.: I know how it works using xml configuration but I would like to do it using annotations since its less verbose.
You can use a Resource like so
#Autowired
public MyClass(#Value("classpath:path/to/a/file") Resource resource) {
// access to resource input stream
}
Note that the #Autowired is required to mark this constructor as the one to use. Otherwise, Spring would look for a parameterless constructor.