What's the magic behind the @Autowired field
I am currently improving my knowledge of Spring. I wonder what actually happens when I use Spring's @Autowire annotation on a field.
Here's a snippet 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 config file contains <context:component-scan base-package="com.xxx.xxx.output"/>
When I execute this code everything works fine. But it surprises me when I remove the setOutputGenerator in the OutPutHelper file, my piece of code continues to work. I figured that with this configuration, the OutputHelper was first created with a default constructor and initialized by the installer.
I was expecting an error because the outputGenerator variable could not be initialized.
Can anyone help me understand?
source to share
The idea of ββhaving margins is @Autowired
dubious. It works, but it will complicate other aspects of your implementation (i.e. Testing).
There are 3 types of injections:
-
- basically configured to apply reflection ( Field.set (Object, Object) ) directly to the field:
@Autowired private MyInterface field;
-
seters - with this approach depending on the configuration of each passes through property (spring passes through all of the methods and performs each of them annotated via
@Autowired
using Method.invoke (Object, Object ...) , so its value is adjusted using it follows setter :@Autowired public void setField(MyInterface value) { this.field = value; }
-
constructors is the latter, and my preferred approach is constructor injection. This basically annotates the constructor with
@Autowired
and instead of using methods or fields, you can customize your bean directly to your constructor. To do this, spring will choose a constructor to be used to instantiate@Component
and it will use the@Autowired
if existent or empty params constructor, calling it with 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 Injection Dependence Injection) is to isolate a piece of code to provide decent support for a test implementation.
To dive deeper, it is necessary to comment that during unit test you want the class in its isolated form, everything you will use with this class is basically mocks for its dependencies (injections).
So what are the results:
- If you are doing field injection , it will be quite costly for each bean to set beans when configuring the bean (need to introduce different logic to set up the bean for testing).
- With the setter injection approach, you will be able to use your own bean to set it up with the mocks needed to isolate your implementation and test its functionality.
- Finally, with a constructor approach , you will not only have support for customizing the bean, but you will be able to require its dependencies. This means that for every new dependency a new parameter is added in your constructor, this brings you design time benefits, for example, you can see during development the unit tests affected by the injection of this new dependency (once your IDE will point to it).
source to share
Simple answer
Actually the setter is useless as it CDI
uses java Reflection to access the fields.
This means that the fields stop making method calls. Reflection allows you to iterate over all the fields in a class and check if they are annotated.
In this case, if a field in your class is annotated with @Autowired
(or @Inject
whichever is more J2E) the container will iterate through the classpath if there is a bean available for that class.
Going deeper
When you start the context, the container iterates over the classes and looks for all fields written with @Inject
or @Autowired
.
For these fields, it looks for an available bean.
Here's a 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 broken example to explain how it works.
Tips
Use @Inject
instead @Autowired
was created by Spring, injection is part of JSR-330. Spring does understand @Inject
, you just need to add the jar dependency javax.inject
to your project.
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
source to share