Overview
I’ve been asked several times to explain the
difference between injecting Spring beans with ‘@Resource’, ‘@Autowired’, and
‘@Inject’. While I received a few opinions from colleagues and read a couple of
posts on this topic I didn’t feel like I had a complete picture.
Annotations
Annotation
|
Package
|
Source
|
@Resource
|
javax.annotation
|
Java
|
@Inject
|
javax.inject
|
Java
|
@Qualifier
|
javax.inject
|
Java
|
@Autowired
|
org.springframework.bean.factory
|
Spring
|
In order to explore the behavior of each annotation I fired up Spring Tool Suite and started debugging the code. I used Spring 3.0.5.RELEASE in my research. The following is a summary of my findings.
The Code
I wanted to know how ‘@Resource’,
‘@Autowired’, and ‘@Inject’ resolved dependencies. I created an interface
called ‘Party’ and created two implementations classes. This allowed me to
inject beans without using the concrete type. This provided the flexibility I
needed to determine how Spring resolves beans when there are multiple type
matches.
public interface Party {
}
|
‘Person’ is a component and it implements
‘Party’.
package com.sourceallies.person;
...
@Component
public class Person implements Party
{
}
|
‘Organization’ is a component and it
implements ‘Party’.
package com.sourceallies.organization;
...
@Component
public class Organization implements Party {
}
|
I setup a Spring context that scans both of
these packages for beans marked with ‘@Component’.
<context:component-scan base-package="com.sourceallies.organization"/>
<context:component-scan base-package="com.sourceallies.person"/>
|
Tests
Test 1: Ambiguous Beans
In this test I injected a ‘Party’ bean that
has multiple implementations in the Spring context.
@Resource
private Party party;
|
@Autowired
private Party party;
|
@Inject
private Party party;
|
In all three cases a
‘NoSuchBeanDefinitionException’ is thrown. While this exception’s name implies
that no beans were found, the message explains that two beans were found. All
of these annotations result in the same exception.
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No unique bean of type [com.sourceallies.Party] is defined:
expected single matching bean but found 2: [organization, person]
|
Test 2: Field Name
In this test I named the Party field person.
By default beans marked with ‘@Component’ will have the same name as the class.
Therefore the name of the class ‘Person’ is person.
@Resource
private Party person;
|
@Autowired
private Party person;
|
@Inject
private Party person;
|
‘@Resource’ can also take an optional ‘name’
attribute. This is equivalent to the ‘@Resource’ code above. In this case the
field variable name remains ‘party’. There is no equivalent syntax for
‘@Autowired’ or ‘@Inject’. Instead you would have to use a ‘@Qualifier’. This
syntax will be covered later.
@Resource(name="person")
private Party party;
|
All four of these styles inject
the ‘Person’ bean.
Test 3: Field Type
In this test I changed the type to be a
‘Person’.
@Resource
private Person party;
|
@Autowired
private Person party;
|
@Inject
private Person party;
|
All of these annotations inject
the ‘Person’ bean.
Test 4: Default Name Qualifier
In this test I use a ‘@Qualifier’ annotation
to point to the default name of the ‘Person’ component.
@Resource
@Qualifier("person")
private Party party;
|
@Autowired
@Qualifier("person")
private Party party;
|
@Inject
@Qualifier("person")
private Party party;
|
All of these annotations inject
the ‘Person’ bean.
Test 5: Qualified Name
I added a ‘@Qualifier’ annotation to the
‘Person’ class
package com.sourceallies.person;
...
@Component
@Qualifier("personBean")
public class Person implements Party
{
}
|
In this test I use a ‘@Qualifier’ annotation
to point to the qualified name of the ‘Person’ component.
@Resource
@Qualifier("personBean")
private Party party;
|
@Autowired
@Qualifier("personBean")
private Party party;
|
@Inject
@Qualifier("personBean")
private Party party;
|
All of these annotations inject
the ‘Person’ bean.
Test 6: List of Beans
In this test I inject a list of beans.
@Resource
private List<Party> parties;
|
@Autowired
private List<Party> parties;
|
@Inject
private List<Party> parties;
|
All of these annotations inject
2 beans into the list. This can also be accomplished with a ‘@Qualifier’. Each
bean marked with a specific qualifier will be added to the list.
Test 7: Conflicting messages
In this test I add a bad ‘@Qualifier’ and a
matching field name.
@Resource
@Qualifier("bad")
private Party person;
|
@Autowired
@Qualifier("bad")
private Party person;
|
@Inject
@Qualifier("bad")
private Party person;
|
In this case the field marked
with ‘@Resource’ uses the field name and ignores the ‘@Qualifier’. As a result
the ‘Person’ bean is injected.
However the ‘@Autowired’ and
‘@Inject’ field throw a ‘NoSuchBeanDefinitionException’ error because it can
not find a bean that matches the ‘@Qualifier’.
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No matching bean of type [com.sourceallies.Party] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true),
@org.springframework.beans.factory.annotation.Qualifier(value=bad)}
|
Conclusions
With the exception of
test 2 & 7 the configuration and outcomes were identical. When I looked
under the hood I determined that the ‘@Autowired’ and ‘@Inject’ annotation
behave identically. Both of these annotations use the ‘AutowiredAnnotationBeanPostProcessor’ to inject
dependencies. ‘@Autowired’ and ‘@Inject’ can be used interchangeable to inject
Spring beans. However the ‘@Resource’ annotation uses the ‘CommonAnnotationBeanPostProcessor’ to inject
dependencies. Even though they use different post processor classes they all
behave nearly identically. Below is a summary of their execution paths.
@Autowired and @Inject
1.
Matches by Type
2.
Restricts by
Qualifiers
3.
Matches by Name
@Resource
1.
Matches by Name
2.
Matches by Type
3.
Restricts by
Qualifiers (ignored if match is found by name)
While it could be argued that ‘@Resource’ will
perform faster by name than ‘@Autowired’ and ‘@Inject’ it would be negligible.
This isn’t a sufficient reason to favor one syntax over the others. I do
however favor the ‘@Resource’ annotation for it’s concise notation style.
@Resource(name="person")
|
@Autowired
@Qualifier("person")
|
@Inject
@Qualifier("person")
|
You may argue that they can be equal concise
if you use the field name to identify the bean name.
@Resource
private Party person;
|
@Autowired
private Party person;
|
@Inject
private Party person;
|
True enough, but what
happens if you want to refactor your
code? By simply renaming the field name you’re no longer referring to the same
bean. I recommend the following practices when wiring beans with annotations.
Spring Annotation Style Best
Practices
1.
Explicitly name your
component [@Component(“beanName”)]
2.
Use ‘@Resource’ with
the ‘name’ attribute [@Resource(name=”beanName”)]
3.
Avoid ‘@Qualifier’
annotations unless you want to create a list of similar beans. For example you
may want to mark a set of rules with a specific ‘@Qualifier’ annotation. This
approach makes it simple to inject a group of rule classes into a list that can
be used for processing data.
4.
Scan specific packages
for components [context:component-scan base-package=”com.sourceallies.person”].
While this will result in more component-scan configurations it reduces the
chance that you’ll add unnecessary components to your Spring context.
Following these guidelines will increase the
readability and stability of your Spring annotation configurations
No comments:
Post a Comment