Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the magic behind Field @Autowired

Tags:

spring

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 ?

like image 943
Thomas Betous Avatar asked Mar 15 '23 05:03

Thomas Betous


1 Answers

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 the 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:

  • If you do field injection, it will be quite costly to every single time set the beans using some reflection to configure the bean during your tests (another logic needs to be introduced to configure the bean to be tested).
  • With setter injection approach you will be able to use your own bean to configure it with mocks necessary to isolate your implementation and test its functionality.
  • And finally, with the constructor injection approach you will have not only the support to configure your bean, but you will be able to require its dependencies. This means that for every new dependency a new parameter on your constructor is added, this brings you come advantages on development time, for example, you will be able to see on development time the unit tests affected with the introduction of that new dependency (once your IDE will point it out for your).
like image 57
Francisco Spaeth Avatar answered Mar 29 '23 09:03

Francisco Spaeth