Hello,
First of all, this is the first time I open an issue, so apologies in advance if I misunderstood the process, let me know and I will happily correct anything that can be :)
Consider the following annotation:
@Autowired(required = false) //Also works with @Nullable
@Retention(RUNTIME)
@Target({ METHOD, FIELD, PARAMETER, TYPE })
public @interface LazyInject {
}
If I use this annotation on an optional field it will behave as expected. But, if I use it inside a constructor, I get the following exception: springStack.log This is in spring 5.3.37, but reading through the code, I think it still works like this in the latest version of spring.
To me this is an inconsistency which makes the framework harder to work with. In my use case, I wanted to make a custom annotation and make it lazy by default, but could not because of this. This means the consumers of this annotation now have to specify by hand that it is lazy in constructors.
So for example, the following does not work:
public class MyService {
@Autowired
public MyService (@LazyInject MyRepository optionalRepo, AnotherDep anotherDep) {... }
}
the following works:
public class MyService {
@Autowired
public MyService (@Autowired(required = false) MyRepository optionalRepo, AnotherDep anotherDep) {... }
}
the following works:
public class MyService {
@LazyInject
MyRepository optionalRepo,
@Autowired
public MyService (AnotherDep anotherDep) {... }
}
Thank you for your help!
Comment From: snicoll
But, if I use it inside a constructor
You mean, something like this?:
public class MyService {
public MyService (@LazyAutowired MyRepository optionalRepo, AnotherDep anotherDep) {... }
}
Comment From: bonnevAI
Yes, exactly, thank you for the quick response.
Comment From: snicoll
You can't use @Autowired
on a constructor parameter. A good way to see this is to replace @LazyAutowired
by @Autowired(required=false)
and you'll see the same behavior.
Autowired works on constructor, to flag which constructor should be used in case of multiple constructors. Once a constructor is selected the full signature is used to perform injection. You can use ObjectProvider
or other means to declare a parameter optional.
See the documentation for more details.
Comment From: sdeleuze
And for @Nullable
the fact you have to specify it on overrides is by design if you are using wider scope (package for example) default to non null, since an override can be used to remove the null-safety constraints.
Comment From: bonnevAI
Hello @snicoll
Apologies, I was not clear enough. Here I am talking about a case which works if annotated at the level of the parameter, but not if the annotation comes from another annotation. I will add the following specific example to the first message to clarify:
public class MyService {
@Autowired
public MyService (@LazyInject MyRepository optionalRepo, AnotherDep anotherDep) {... }
}
Does not work
public class MyService {
@Autowired
public MyService (@Autowired(required = false) MyRepository optionalRepo, AnotherDep anotherDep) {... }
}
Works
public class MyService {
@LazyInject
MyRepository optionalRepo,
@Autowired
public MyService (AnotherDep anotherDep) {... }
}
Works as well.
The behavior is exactly the same for @Nullable
instead of @Autowired(required = false)
.
Comment From: snicoll
Sorry that I sent you in the wrong direction. I've learned in the process that @Autowired(required = false)
works on constructor parameters. @jhoeller and I discussed this and the conclusion is that we tolerate that usage with no intention to increase its coverage (such as meta-annotation support).
You should really use Optional
or ObjectProvider
to indicate that the parameter is optional.
Comment From: bonnevAI
Hello @snicoll,
Thank you for your response, no problem. Unfortunately It's not possible for me to use Optional or ObjectProvider for backward compatibility reasons, it is of course the first thing I tried to use. Do you think it is ok if I try to enrich the meta-data support of the framework? It is often a pain point for me as it can be a bit inconsistent.
Of course there is little chance I find the time for it, but in the event I do, I prefer to check first if it makes sense for you before working on it :)
Thank you @snicoll @jhoeller for spending some time on this!
Comment From: snicoll
Do you think it is ok if I try to enrich the meta-data support of the framework?
Like I mentioned already, we tolerate that use case and we have no intention to extend it.