Field injection involves directly annotating private fields of a class with @Autowired
. Here’s an example:
@Component
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order findOrderById(Long id) {
return orderRepository.findById(id);
}
}
Field injection complicates the unit testing of your components. Since dependencies are directly injected into the fields, you cannot easily provide mocks or alternative implementations outside of the Spring context.
Lets take the same example of the sameOrderService
class.
If you wish to unit test the OrderService
, you’d face difficulty in mocking the OrderRepository
because it’s a private field. Here’s a way to unit test OrderService
:
@RunWith(SpringJUnit4ClassRunner.class)
public class OrderServiceTest {
private OrderService orderService;
@Mock
private OrderRepository orderRepository;
@Before
public void setUp() throws Exception {
orderService = new OrderService();
// This will set the mock orderRepository into orderService's private field
ReflectionTestUtils.setField(orderService, "orderRepository", orderRepository);
}
...
}
Though possible, using Reflection to replace the private fields is not a great design. It contradicts object-oriented design principles and makes tests difficult to read and maintain.
On the other hand, with constructor injection:
@Component
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
}
You can easily provide a mock OrderRepository
during testing:
OrderRepository mockRepo = mock(OrderRepository.class);
OrderService orderService = new OrderService(mockRepo);
Field injection makes your beans mutable post-construction. With constructor injection, once an object is constructed, its dependencies remain unchanged.
Example:
The field-injected class:
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
}
Here, the userRepository
reference can be re-assigned after the object has been created, breaking the immutability principle.
If we use constructor injection:
@Component
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
The userRepository
field is can be declared final and made immutable post-construction.
Field injection makes your classes more tightly coupled to Spring, as it uses Spring-specific annotations (@Autowired
) directly on your fields. This can introduce problems in below scenarios:
UserService
logic. In this context, the @Autowired
annotation means nothing and can’t be used to inject dependencies. You’d have to either refactor the class or implement cumbersome workarounds to reuse UserService
.@Autowired
becomes an obstacle. You’d have to refactor every place where you’ve used Spring-specific annotations, which can be a tedious process.@Autowired
annotation might be confusing. They might wonder how dependencies are resolved, leading to a steeper learning curve.When a class utilizes field injection and is instantiated via its default constructor, the dependent fields remain uninitialized.
Example:
@Component
public class PaymentGateway {
@Autowired
private PaymentQueue paymentQueue;
public void initiate (PaymentRequest request){
paymentQueue.add(request);
...
}
}
public class PaymentService {
public void process (PaymentRequest request) {
PaymentGateway gateway = new PaymentGateway();
gateway.initiate(request);
}
}
Thus, if PaymentGateway
is accessed at runtime in this state, a NullPointerException
will occur. The only way to manually initialize these fields outside of the Spring context would be to use reflection, which is not advisable for various reasons.
Field injection might mask circular dependencies problems, making them harder to catch during development.
Example:
Consider two services, AService
and BService
, that depend on each other:
@Service
public class AService {
@Autowired
private BService bService;
}
@Service
public class BService {
@Autowired
private AService aService;
}
The above can lead to unexpected behavior and problems in the application.
Using constructor injection, Spring would immediately throw a BeanCurrentlyInCreationException
during startup, making you aware of the circular dependency. To solve the circular dependency problem, however, you can lazily load one of the dependencies using @Lazy.
While field injection might seem more concise, its disadvantages far outweigh its brevity. Constructor injection provides clear advantages in terms of testability, immutability, and overall robustness of your application. It aligns well with the SOLID principles, ensuring your Spring Boot applications are maintainable and less error-prone.