# 의존성 주입의 기본 개념
먼저, 의존성 주입이란 클래스가 필요로 하는 외부 객체를 Spring 컨테이너가 대신 주입해주는 개념입니다. 이렇게 하면 클래스는 직접 의존성을 생성할 필요 없이, Spring이 적절한 시점에 해당 객체를 주입해주므로 유연한 애플리케이션 구조를 만들 수 있습니다.
Spring에서 의존성 주입을 할 수 있는 방법에는 크게 두 가지가 있습니다:
- 필드 주입: @Autowired로 필드에 주입.
@Component
public class MyService {
// 필드 주입
@Autowired
private MyRepository myRepository;
}
- 생성자 주입: 생성자를 통해 주입.
@Component
public class MyService {
// 생성자 주입
private final MyRepository myRepository;
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
}
여기서 가장 권장되는 방식이 생성자 주입입니다. 이제 왜 생성자 주입이 필드 주입보다 더 나은지 살펴보겠습니다.
# 필드 주입의 문제점
저 역시 처음에는 간단해 보이는 필드 주입을 많이 사용했습니다. 하지만, 필드 주입에는 몇 가지 중요한 문제가 있습니다.
빈 생성 순서에 의존
Spring은 애플리케이션이 시작될 때, 모든 빈(Bean)을 자동으로 생성하고 각 빈이 필요로 하는 의존성을 주입합니다. 하지만 필드 주입 방식을 사용할 때는 빈 생성 순서에 따라 문제가 발생할 수 있습니다.
필드 주입의 위험성
필드 주입에서는 클래스 A가 생성된 후에, Spring이 클래스 A의 필드에 B 클래스의 객체를 주입합니다. 이 과정에서 중요한 점은 Spring 컨테이너가 B 클래스의 빈을 이미 생성했는지 여부입니다. 만약 B 클래스의 빈이 아직 생성되지 않았거나, 아예 빈으로 등록되어 있지 않다면, 클래스 A의 필드에 B 객체가 주입되지 않고 null로 남게 됩니다.
@Component
public class A {
@Autowired
private B b;
public void doSomething() {
b.someMethod(); // b가 null이면 NullPointerException 발생
}
}
위 코드에서 A 클래스는 B 클래스의 메서드 someMethod() 를 호출하려고 합니다. 그러나, 만약 B 클래스의 빈이 아직 생성되지 않았다면, A 클래스의 b 필드는 null 로 남아 있게 됩니다. 이 상태에서 someMethod() 를 호출하려고 하면 NullPointerException이 발생하게 됩니다. 이 문제는 필드 주입에서 빈 생성 순서에 따라 빈이 주입되지 않을 때 발생하는 대표적인 예입니다.
# 생성자 주입의 장점
생성자 주입의 장점 중 하나는 바로 이런 빈 생성 순서 문제를 방지할 수 있다는 점입니다. 생성자 주입을 사용하면, 클래스가 생성될 때 필요한 모든 의존성이 이미 주입되었음을 보장할 수 있기 때문에, 위에서 설명한 null 문제를 피할 수 있습니다.
의존성 주입의 일관성 보장
생성자 주입은 클래스의 객체가 생성되는 시점에 필요한 모든 의존성을 주입받기 때문에, 객체가 생성된 이후에는 항상 완전한 상태로 보장됩니다. 즉, 필요한 의존성 없이 인스턴스가 생성될 일이 없다는 뜻입니다. 이로 인해 **불변성(immutability)**이 보장됩니다. 반면 필드 주입은 클래스가 생성된 후 나중에 의존성이 주입되기 때문에 주입 시점이 불명확할 수 있습니다.
테스트 용이성
생성자 주입은 테스트에서도 강력한 장점을 발휘합니다. 생성자 주입을 사용하면, 테스트 코드에서 의존성을 쉽게 주입하거나 교체할 수 있습니다. 필드 주입을 사용하면 주입된 필드에 접근하거나 모의 객체(mock)를 주입하는 것이 복잡해집니다.
# 생성자 주입의 순환 의존성
Q: 그렇다면 여기서 드는 질문 각 클래스 A,B가 서로서로 생성자주입을 하게 되면 어떻게 될까?
A: 서로 의존하는 클래스 A와 B가 동시에 생성자 주입을 사용하여 서로를 의존하게 되면, 순환 의존성(circular dependency) 문제가 발생하게 됩니다. 이 경우 Spring 컨테이너는 이러한 순환 의존성을 해결하지 못해 애플리케이션이 시작될 때 오류를 발생시킵니다.
@Component
public class A {
private final B b;
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private final A a;
public B(A a) {
this.a = a;
}
}
위 코드에서 A 클래스는 B 클래스를, B 클래스는 A 클래스를 생성자 주입으로 의존하고 있습니다. 이 경우 Spring은 A 또는 B를 먼저 생성해야 하지만, 두 클래스 모두 생성자에서 서로의 객체를 필요로 하므로, 어느 것도 먼저 생성할 수 없는 상황에 빠지게 되어 아래와 같은 오류가 발생합니다.
======Error creating bean with name 'A': Requested bean is currently in creation: Is there an unresolvable circular reference?=====
생성자 주입의 순환 의존성 해결방안
필드 주입이나 @Autowired로 수정, @Lazy를 사용하는 방법이 있지만 다 필요 없고 설계를 수정해서 순환 의존성이 발생하지 않게 해야합니다.
# @RequiredArgsConstructor
생성자 주입을 더 깔끔하게 만들기 위해, Lombok 라이브러리를 사용할 수 있습니다. Lombok의 @RequiredArgsConstructor 어노테이션을 사용하면, final 필드에 대해 자동으로 생성자를 만들어 줍니다.
// 기존코드
@Component
public class MyService {
private final MyRepository myRepository;
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
}
// @RequiredArgsConstructor 사용
@RequiredArgsConstructor
@Component
public class MyService {
private final MyRepository myRepository;
}
}
'Java' 카테고리의 다른 글
[Spring] 왜 자주 사용하는 객체들을 자동으로 빈으로 등록하지 않을까? (0) | 2024.08.30 |
---|---|
[Redis] 직렬화 (0) | 2024.08.26 |
[Bean, Proxy] 왜 @Cacheable이 동작하지 않을까? (0) | 2024.08.21 |
[Redis] Redis란? (0) | 2024.08.16 |
[String Boot] JWT 토큰 저장 HTTP-only Cookie (0) | 2024.08.09 |