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?

+3


source to share


2 answers


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).
+3


source


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>

      

+3


source







All Articles