ChatGPT解决这个技术问题 Extra ChatGPT

Spring @Autowire on Properties vs Constructor

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?


g
gfullam

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.


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!
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.
dont know how I saw everything except he blog post line. Thanks for that!
What if you need a second constructor do you need to use @Autowire or can you use two constructors?
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.
d
developer

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.


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.
@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
s
stinger

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;
    }
}

Interesting, really curious as to how does spring inject the dependency in this case ?
@el Mowgli - Spring scans your classes for constructor that matches your class' fields. Find details here: docs.spring.io/spring/docs/current/spring-framework-reference/…
... which seems to mean that constructor injection became the default injection mechanism. Still you need the constructor with all the dependencies explicitly passed
You can use Lombok's annotation: @RequiredArgsConstructor instead of the explicit constructor.
@MarcusViníciusVoltolim how your comment about lombok is related to Spring autowiring technique?
D
Daniel Perník

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.


Good to know but removing an annotation does not improve readability much.
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
D
Dougie T

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


S
Salvatore Pannozzo Capodiferro

I hope I won't be downgraded for expressing my opinion, but for me option A better reflects the power of Spring dependency injection, while in the option B you are coupling your class with your dependency, in fact you cannot instantiate an object without passing its dependencies from the constructor. Dependency Injection have been invented for avoid that by implementing Inversion of Control,so for me option B doesn't have any sense.


A
Atul Dwivedi

Autowired constructors provides a hook to add custom code before registering it in the spring container. Suppose SomeService class extends another class named SuperSomeService and it has some constructor which takes a name as its argument. In this case, Autowired constructor works fine. Also, if you have some other members to be initialized, you can do it in the constructor before returning the instance to spring container.

public class SuperSomeService {
     private String name;
     public SuperSomeService(String name) {
         this.name = name;
     }
}

@Component
public class SomeService extends SuperSomeService {
    private final SomeOtherService someOtherService;
    private Map<String, String> props = null;

    @Autowired
    public SomeService(SomeOtherService someOtherService){
        SuperSomeService("SomeService")
        this.someOtherService = someOtherService;
        props = loadMap();
    }
}

J
Java Developer

I prefer construction injection, just because I can mark my dependency as final which is not possible while injecting properties using property injection.

your dependencies should be final i.e not modified by program.


why not? we may not mark bean property as final?
i
igor.zh

There are few cases when @Autowired is preferable. One of them is circular dependency. Imagine the following scenario:

@Service
public class EmployeeService {
    private final DepartmentService departmentService;

    public EmployeeService(DepartmentService departmentService) {
        this.departmentService = departmentService;
    }
}

and

@Service
public class DepartmentService {
    private final EmployeeService employeeService;

    public DepartmentService(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }
}

Then Spring Bean Factory will throw circular dependency exception. This won't happen if you use @Autowired annotation in both beans. And this is understandable: the constructor injection happens at very early stage of Spring Bean initialization, in createBeanInstance method of Bean Factory, while @Autowired-based injection happens way later, on post processing stage and is done by AutowiredAnnotationBeanPostProcessor. Circular dependency is quite common in complex Spring Context application, and it needs not to be just two beans referring one another, it could a complex chain of several beans.

Another use case, where @Autowired is very helpful, is self-injection.

@Service
public class EmployeeService {
    
    @Autowired
    private EmployeeService self;

}

This might be needed to invoke an advised method from within the same bean. Self-injection is also discussed here and here.