For example, if I have a UserService that has a dependency on UserRepository, I can have the UserRepository injected using @Autowired annotation like this:
UserRepository Class...
class UserRepository { UserRepository () {} }
UserService Class...
class UserService { @Autowired private UserRepository userRepository; UserService () {} }
This is done using Field Injection. The same thing can be accomplied using Setter Injection:
class UserService { private UserRepository userRepository; UserService () {} @Autowired // Using setter injection public void setUserRepository( UserRepository userRepository) { this.userRepository = userRepository } }
or via Constructor Injection:
class UserService { private UserRepository userRepository; @Autowired // Using constructor Injection UserService (UserRepository userRepository) { this.userRepository = userRepository } }
There are different opinions on which method is "the right way" but since this post is not about that debate. I would point you to this post instead: Why I changed My Mind About Field Injection
Which ever method you choose to use, you are essentially doing the same thing: instructing Spring to supply an object's dependency by using the @Autowired annotation.
But what happens when the Object you want injected in has a constructor that requires an argument?
In our example, what if the implementation of the UserRepository requires an entityManager to be supplied as a constructor argument. How do you go about this? Is there a propetry of the @Autowired annotation that allows you to specify the argument constructors?
Something like this:
class UserService { @Autowired(constructor="specify constructor for UserRepository before injecting it into UserService") private UserRepository userRepository; UserService () {} } }
Unfortunately no, there is nothing like this, and if you think about it, rightly so. Because at the point of autowiring a bean, the bean should be in existence already, created by the Spring Application context. What the @Autowired annotation is doing is simplying taking what is already there (not creating it) and make it part of the dependency of another bean.
If you then want to autowire a bean with constructor argument in Spring, then you have to look at doing so, at the creation process for that bean.
So in our example above, if UserRepository has a dependency that is supplied via its constructor, you make sure this constructor argument is made known and supplied, before it can then be @Autowired into our UserService.
And this can be done either by using the @Autowired annotation or the @Value annotation. You use the @Autowired notation when the constructor argument is another Object, while the @Value annotation comes in handy when the contructor argument can easily be evaluated using Spring expression.
Using @Autowired
class UserRepository { @Autowired UserRepository (EntityManager entityManager) { // initalize the UserRepository object // using supplied EntityManager object } }
Using @Value
This is useful when the value of the contructor arguments can be evaluated from property files or other sources for that matter. Let us imagine the arguments needed for UserRepository are JDBC configurations. So when you want to autowire it into the UserService class, you want it to already have the needed JDBC properties passed on. As we stated earlier, to do this, we look into UserRepository itself and its creation point, not UserService.
Let us imagine the JDBC property file looks thus:
jdbc.url=jdbc:mysql://localhost/test jdbc.username=user jdbc.password=userpass
Then you can have these value passed in using @Value:
class UserRepository { @Autowired UserRepository (@Value("${jdbc.url}") String url, @Value("${jdbc.usersame}") String username, @Value("${jdbc.password}") String password) { // initalize the UserRepository object // using supplied JDBC properties } }With the above, you can happily now autowire the UserRepository into UserService and the constructor argument contract would be gracefully fulfilled.
Note that for the above property placeholder evaluation to be done, you have to have the PropertyPlaceholderConfigurer configured in your Spring application context. You can find more about this here: Hiding of Bean Definition via Namespaces
Hi
ReplyDeleteThanks for the post. I got a question that i have been trying to answer for a while now; would be glad to have your help/input. Question is this:
I have a bean say, PersonImpl that needs to be injected into another bean say, Employee. Now PersonImpl needs a parameter to be instantiated and this parameter, i cannot supply through XML config or through a properties file. But it needs to be supplied dynamically so my idea was as follows:
@Named //create it as a bean
....PersonImpl
//have a custom annotation
...@MyAnno(){
String name();
}
//In the Employee bean do
@Inject
@MyAnno(name="test")
public void setPerson(PersonImpl p){
this.p = p;
}
//And there would be another bean implementing BeanPostProcessor that reads the value of 'name' from the @MyAnno annotation and creates an instance of PersonImpl using this value. Now am expecting @Inject to pick up the PersonImpl bean instantiated by the BeanPostProcessor. But it does not seem to be working as expected. Where am i going wrong? My understanding is the BeanPostProcessor would create a bean that @Inject should pick up, is it incorrect? Looks like @Inject picks up an instance of the bean before the post-processing happens, why!
so obvious now that I see it, but this was really helpful, thank you-
ReplyDeleteUserRepository is third party class then how could we inject argument constructor bean.
ReplyDeletenothing to say... but only one word ...super
ReplyDeleteNeat explanation.
ReplyDeleteIn this code:
ReplyDeleteclass UserRepository {
@Autowired
UserRepository (@Value("${jdbc.url}") String url,
@Value("${jdbc.usersame}") String username,
@Value("${jdbc.password}") String password) {
// initalize the UserRepository object
// using supplied JDBC properties
}
}
I don't think you should be having @Autowired in the constructor. We are using @Value to initialize the arguments of the constructor.
Hi, thanks for the nice article, I have a question though, what if constructor argument are not static, it gets evaluated at run time and needs to pass dynamicaly? Is there any work around this?
ReplyDeleteHow are the arguments for the constructor set? I have autowired the field UserRepository in the UserService class. And I have autowired the constructor on the UserRepository. When i call the function of UserRepository from UserService I get below error
ReplyDeleteCaused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.business.ContactUs com.controller.MainController.contactUs; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'contactUs' defined in file [D:\Apache\tomcat-web13\webapps\ROOT\WEB-INF\classes\com\business\ContactUs.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [java.lang.String]: : No qualifying bean of type [java.lang.String] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [java.lang.String] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
Very clearly an detailed explanation. Hats-off to you.
ReplyDelete