Reputation: 5123
I am currently improving my Spring knowledge. I wonder what really happens when I use Spring annotation @Autowire on a field.
Here is a piece of code :
OutputHelper file
@Component
public class OutputHelper {
@Autowired
@Qualifier("csvOutputGenerator")
private IOutputGenerator outputGenerator;
public void setOutputGenerator(IOutputGenerator outputGenerator) {
this.outputGenerator = outputGenerator;
}
// I can focus only on what my code do because my objects are injected
public void generateOutput(){
outputGenerator.generateOutput();
}
}
CsvOutputGenerator file
@Component
public class CsvOutputGenerator implements IOutputGenerator {
public void generateOutput(){
System.out.println("Csv Output Generator");
}
}
Application file
public static void main(String[] args) {
// Create the spring context
ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/spring-module.xml");
// Get the configured OutpuHelper from the spring-module.xml
OutputHelper output = (OutputHelper) context.getBean("outputHelper");
// Display output from the output configured
output.generateOutput();
}
My configuration file just contain <context:component-scan base-package="com.xxx.xxx.output"/>
When I execute this code all work fine. But what makes me surprised is when I delete the setOutputGenerator in OutPutHelper file, my piece of code keeps working. I tought that with this configuration, the OutputHelper was first created with default constructor and initialized with setter.
I expected an error because the variable outputGenerator was not be able to be initialized.
Is anyone can help me to understand ?
Upvotes: 4
Views: 1877
Reputation: 23913
The idea to have fields @Autowired
is questionable. It works, but it will difficult other aspects of your implementation (i.e. testing).
There are 3 types of injections:
fields - basically configured applying reflection (Field.set(Object, Object)) directly to the field:
@Autowired
private MyInterface field;
setters - with this approach the configuration of each dependency goes through a property (spring goes through all methods and execute each one annotated with @Autowired
using Method.invoke(Object, Object...), thus its value is configured using its setter as follows:
@Autowired
public void setField(MyInterface value) {
this.field = value;
}
constructors - the last, and my preferable approach, the constructor injection. That one basically annotates an constructor with @Autowired
and instead of using methods or fields, you can configure your bean directly on your constructor. For that spring will elect a constructor to be used to instantiate your @Component
, and it will use an @Autowired
if existent or a empty params constructor, invoking it using Constructor.newInstance(Object...). Example:
@Component
public class Implementation {
private MyInterface field;
@Autowired
public Implementation(MyInterface value) {
Assert.notNull(value, "value should not be null");
this.field = value;
}
}
One of the ideas behind Inversion of Control (or Dependence Injection) is to be able to isolate a piece of code in order to provide decent test implementation support.
In order to go deeper, it is necessary to comment that during a unit test you want the class in its isolated form, all you will use with that class are basically mocks for its dependencies (injections).
So, what are the results:
Upvotes: 4
Reputation: 7872
Actually, the setter is useless, since the CDI
use java Reflection to access fields.
It means that fields are no longer accessed by method calls. Reflection allow iterating throught all fields of a class and check if there are annoted with a specific annotation.
In this case, if a field in your class is annoted With @Autowired
(or @Inject
wich is more J2E complient), the container will iterate throught searching if there is a registered bean that fits the current property.
When you context is starting, the container iterate classes and search all field annoted with @Inject
or @Autowired
.
For these fields, it search an available bean.
Here is the must simple example :
public class SpringClassInChargeOfDependencyInjection {
public void handdleInjections(T objectWithInjectableField) {
Class<T> clazz = objectWithInjectableField.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class) || field.isAnnotationPresent(Inject.class)) {
//find a bean for the type;
Object injectableBean = getAvailablebean(field.getType());
field.setAccessible(true);
//inject the value into the class, this line explain why the setter is not necessary
field.set(objectWithInjectableField, injectableBean);
}
}
}
}
This is a non-working example just to explain how it works.
You might consider using @Inject
instead of @Autowired
, the later was created by Spring, @Inject
is a part of the the JSR-330. Spring does understand @Inject
as well, you just need to add the javax.inject
jar dependency to your project. If later you want to switch from spring to something else (guice for example) you won't have to change all your @Autowired
annotations
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
Upvotes: 3