Spring Framework는 의존성 주입(Dependency Injection, DI)이라는 개념을 사용하여 객체 간의 결합도를 낮추고, 코드의 유연성과 테스트 용이성을 높이는 것을 목표로 합니다. @Autowired
어노테이션은 Spring에서 의존성 주입을 간편하게 처리하기 위해 사용되는 대표적인 방법입니다. 하지만, @Autowired
를 사용하는 것이 항상 최선의 선택이 아닐 수 있습니다. 이 블로그 글에서는 @Autowired
를 사용하지 말아야 하는 이유에 대해 설명하겠습니다.
1. 명시적인 의존성 주입이 불가능
@Autowired
를 사용하면 의존성을 자동으로 주입하게 되지만, 이는 의존성이 명확하지 않다는 단점이 있습니다. 예를 들어, 클래스 A가 클래스 B에 의존한다고 가정할 때, @Autowired
를 사용하면 A
클래스의 생성자나 메서드에서 의존성 주입이 자동으로 처리되므로 클래스 B가 A
의 의존성임을 명확하게 알기 어려워집니다.
해결 방법: 생성자 주입
생성자 주입을 사용하면 의존성 주입이 명확해지고, 객체 생성 시 의존성을 바로 알 수 있습니다. 이는 코드의 가독성과 유지보수성을 높여줍니다.
public class A {
private final B b;
@Autowired
public A(B b) {
this.b = b;
}
}
이렇게 생성자 주입을 사용하면 의존성 관계가 명확하게 드러나고, 코드가 더 안전해집니다.
2. 테스트 용이성 저하
@Autowired
를 사용하면 의존성이 자동으로 주입되므로, 해당 클래스의 테스트가 어려워질 수 있습니다. 테스트를 작성할 때 @Autowired
가 의존성 주입을 자동으로 처리하기 때문에, 필요한 mock 객체나 더미 객체를 직접 주입하는 것이 어려워질 수 있습니다.
해결 방법: 명시적인 의존성 주입
명시적으로 의존성을 주입하는 방식(특히 생성자 주입)을 사용하면, 테스트 시 mock 객체를 주입하거나 의존성을 쉽게 제어할 수 있습니다.
@RunWith(MockitoJUnitRunner.class)
public class ATest {
@InjectMocks
private A a;
@Mock
private B b;
@Test
public void testMethod() {
// test logic
}
}
이 방식으로 의존성을 쉽게 모킹(mocking)할 수 있어 테스트 작성이 용이해집니다.
3. 순환 의존성 문제
@Autowired
를 사용할 때 순환 의존성 문제가 발생할 수 있습니다. 예를 들어, 클래스 A가 클래스 B를 의존하고, 클래스 B가 다시 클래스 A를 의존하는 상황에서, Spring은 순환 의존성을 해결하지 못할 수 있습니다. 이 경우, @Autowired
가 자동으로 의존성을 주입하려 할 때 오류가 발생하게 됩니다.
해결 방법: 생성자 주입 및 순환 의존성 관리
순환 의존성은 생성자 주입을 통해 쉽게 해결할 수 있습니다. 순환 의존성이 발생하지 않도록 구조를 변경하거나, 경우에 따라 @Lazy
를 사용하여 지연 로딩 방식으로 해결할 수 있습니다.
4. null 안전성 문제
@Autowired
를 사용할 때, 의존성이 주입되지 않는 경우가 있을 수 있습니다. @Autowired
는 기본적으로 의존성을 찾지 못하면 NullPointerException
을 발생시킬 수 있습니다. @Autowired(required = true)
속성을 사용하면 필수 의존성으로 처리할 수 있지만, 여전히 명시적이지 않기 때문에 문제가 발생할 수 있습니다.
해결 방법: 생성자 주입 및 @NonNull
사용
생성자 주입을 사용하면 객체가 생성될 때 의존성이 반드시 주입되므로 NullPointerException
이 발생하지 않습니다. 또한, @NonNull
과 같은 어노테이션을 활용하여 null 안전성을 보장할 수 있습니다.
public class A {
private final B b;
@Autowired
public A(@NonNull B b) {
this.b = b;
}
}
5. 유연성 부족
@Autowired
는 자동 주입을 통해 의존성을 관리하지만, 의존성의 주입 방식을 변경하거나 제어하는 데 제한이 있을 수 있습니다. 특히, 여러 구현체가 있을 경우 어떤 구현체를 주입할지 명시적으로 선택할 수 없게 됩니다.
해결 방법: @Qualifier
와 인터페이스 사용
여러 구현체가 있을 때는 @Qualifier
를 사용하여 명시적으로 의존성을 주입할 수 있습니다. 그러나 @Qualifier
를 사용하는 것 자체가 조금 더 복잡성을 더할 수 있습니다.
@Autowired
@Qualifier("myImplementation")
private MyInterface myInterface;
하지만, 이런 방식이 적절하게 관리되지 않으면 코드가 불필요하게 복잡해질 수 있습니다.
6. 스프링 버전 간 호환성 문제
@Autowired
는 Spring Framework의 여러 버전에서 사용될 수 있지만, 특정 버전에서는 의존성 주입 처리 방식이 다를 수 있습니다. 이로 인해 특정 버전에서만 작동하는 코드가 생길 수 있습니다. 예를 들어, Spring 5 이후에는 생성자 주입을 권장하고 있지만, 이전 버전에서는 필드 주입을 사용한 코드가 많았기 때문에 버전 간 호환성 문제를 야기할 수 있습니다.
해결 방법: 표준화된 의존성 주입 방식 사용
Spring의 권장 방안인 생성자 주입을 사용하여 버전 간 호환성 문제를 최소화할 수 있습니다. 생성자 주입은 Spring의 모든 버전에서 일관된 방식으로 동작하므로, 버전 업그레이드 시 발생할 수 있는 문제를 방지할 수 있습니다.
결론
@Autowired
는 Spring에서 매우 유용하게 사용될 수 있는 어노테이션이지만, 무조건 사용하는 것이 최선의 선택은 아닙니다. 생성자 주입을 사용하면 의존성 주입이 명확하고, 테스트 작성이 쉬워지며, 순환 의존성 문제를 예방할 수 있습니다. 따라서, Spring에서는 가능하면 @Autowired
대신 생성자 주입을 사용하고, @Autowired
의 사용을 최소화하는 것이 더 좋은 설계로 이어질 수 있습니다.