Spring @Autowire on Properties vs Constructor

123,606

Solution 1

Yes, option B (which is called constructor injection) is actually recommended over field injection, and has several advantages:

  • the dependencies are clearly identified. There is no way to forget one when testing, or instantiating the object in any other circumstance (like creating the bean instance explicitly in a config class)
  • the dependencies can be final, which helps with robustness and thread-safety
  • you don't need reflection to set the dependencies. InjectMocks is still usable, but not necessary. You can just create mocks by yourself and inject them by simply calling the constructor

See this blog post for a more detailed article, by one of the Spring contributors, Olivier Gierke.

Solution 2

I will explain you in simple words:

In Option(A), you are allowing anyone (in different class outside/inside the Spring container) to create an instance using default constructor (like new SomeService()), which is NOT good as you need SomeOtherService object (as a dependency) for your SomeService.

Is there anything else the autowired constructor does besides add code to the unit tests? Is this a more preferred way to do dependency injection?

Option(B) is preferred approach as it does NOT allow to create SomeService object without actually resolving the SomeOtherService dependency.

Solution 3

Please note, that since Spring 4.3 you don't even need an @Autowired on your constructor, so you can write your code in Java style rather than tying to Spring's annotations. Your snippet would look like that:

@Component
public class SomeService {
    private final SomeOtherService someOtherService;

    public SomeService(SomeOtherService someOtherService){
        this.someOtherService = someOtherService;
    }
}

Solution 4

Good to know

If there is only one constructor call, there is no need to include an @Autowired annotation. Then you can use something like this:

@RestController
public class NiceController {

    private final DataRepository repository;

    public NiceController(ChapterRepository repository) {
        this.repository = repository;
    }
}

... example of Spring Data Repository injection.

Solution 5

Actually, In my experience, The second option is better. Without the need for @Autowired. In fact, it is wiser to create code that is not too tightly coupled with the framework (as good as Spring is). You want code that tries as much as possible to adopt a deferred decision-making approach. That is as much pojo as possible, so much such that the framework can be swapped out easily. So I would advise you create a separate Config file and define your bean there, like this:

In SomeService.java file:

public class SomeService {
    private final SomeOtherService someOtherService;

    public SomeService(SomeOtherService someOtherService){
        this.someOtherService = someOtherService;
    }
}

In ServiceConfig.java file:

@Config
public class ServiceConfig {
    @Bean
    public SomeService someService(SomeOtherService someOtherService){
        return new SomeService(someOtherService);
    }
}

In fact, if you want to get deeply technical about it, there are thread safety questions (among other things) that arise with the use of Field Injection (@Autowired), depending on the size of the project obviously. Check this out to learn more on the advantages and disadvantages of Autowiring. Actually, the pivotal guys actually recommend that you use Constructor injection instead of Field Injection

Share:
123,606

Related videos on Youtube

GSUgambit
Author by

GSUgambit

Software Developer

Updated on July 08, 2022

Comments

  • GSUgambit
    GSUgambit almost 2 years

    So since I've been using Spring, if I were to write a service that had dependencies I would do the following:

    @Component
    public class SomeService {
         @Autowired private SomeOtherService someOtherService;
    }
    

    I have now run across code that uses another convention to achieve the same goal

    @Component
    public class SomeService {
        private final SomeOtherService someOtherService;
    
        @Autowired
        public SomeService(SomeOtherService someOtherService){
            this.someOtherService = someOtherService;
        }
    }
    

    Both of these methods will work, I understand that. But is there some advantage to using option B? To me, it creates more code in the class and unit test. (Having to write constructor and not being able to use @InjectMocks)

    Is there something I'm missing? Is there anything else the autowired constructor does besides add code to the unit tests? Is this a more preferred way to do dependency injection?

  • GSUgambit
    GSUgambit over 7 years
    So let's dive deeper and say you have some other properties like @Value("some.prop") private String property; Would you put this into the constructor as well? It just seems as if you would end up with very long fully parameterized constructors. Which isn't bad perse, just far more code. I definititely appreciate your comment!
  • JB Nizet
    JB Nizet over 7 years
    Yes, you would put that in the constructor, too. As the linked article says, when the constructor starts having too many parameters, it's often the sign that you should split the class in smaller classes with less responsibilities, and less dependencies.
  • GSUgambit
    GSUgambit over 7 years
    dont know how I saw everything except he blog post line. Thanks for that!
  • powder366
    powder366 about 7 years
    What if you need a second constructor do you need to use @Autowire or can you use two constructors?
  • JB Nizet
    JB Nizet about 7 years
    You can have as many constructors as you want, but only one of them can be annotated with Autowired, and will be called by Spring.
  • Chris Knoll
    Chris Knoll over 5 years
    With option B, how is this not a 'leaky abstraction'? If I don't know beforehand what the actual implementation is of some service, and Impl1 has dependencies A/B/C and Impl2 has A/B/F/G, the constructor approach would require me to know the dependencies up front. But, with @autowire, when i attempt to consume a service, if it has all the necessary dependencies registered in the container, then I'm good, and I didn't have to know anything about the dependencies of the service.
  • javaBean007
    javaBean007 over 5 years
    @ChrisKnoll The point is to know that the dependencies are required for your service to work(especially for testing!). It's not a 'leaky abstraction' to know that your Car needs a Key to start. You don't need to know the exact implementation of Key but you do need to know you need one. Also, as long as you also have your service in question(SomeService) annotated say(Service), then you won't need to know what it's dependencies are. In your class(that consumes the service) have the same autowire annotation for it and you can call SomeService.perform() without calling 'new' keyword
  • Nisarg Patil
    Nisarg Patil over 5 years
    does @Autowired on a property internally does setter injection?
  • JB Nizet
    JB Nizet over 5 years
    @NisargPatil Autowired on a field sets the fiels directly, using reflection, without going through any setter. Autowired on a setter calls the setter.
  • Nitin Gaur
    Nitin Gaur over 4 years
    Good to know but removing an annotation does not improve readability much.
  • Mr Matrix
    Mr Matrix over 4 years
    Interesting, really curious as to how does spring inject the dependency in this case ?
  • stinger
    stinger over 4 years
    @el Mowgli - Spring scans your classes for constructor that matches your class' fields. Find details here: docs.spring.io/spring/docs/current/spring-framework-referenc‌​e/…
  • Novaterata
    Novaterata over 3 years
    I would much rather have this than a hundred redundant Autowired when there is already another annotation marking the class as part of the DI system
  • Andrey M. Stepanov
    Andrey M. Stepanov over 2 years
    ... which seems to mean that constructor injection became the default injection mechanism. Still you need the constructor with all the dependencies explicitly passed
  • Andrey M. Stepanov
    Andrey M. Stepanov over 2 years
    why not? we may not mark bean property as final?