Thomas Betous
Thomas Betous

Reputation: 5123

What is the magic behind Field @Autowired

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

Answers (2)

Francisco Spaeth
Francisco Spaeth

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:

  • 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).

Upvotes: 4

Anthony Raymond
Anthony Raymond

Reputation: 7872

Simple answer

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.


Going deeper

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.

Tips

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

Related Questions