일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 필드 주입
- 일괄처리
- Dispatcher Servlet
- spring boot
- 이펙티브 자바
- 불변 객체
- Spring Batch
- View Resolver
- 가변 객체
- 참조 타입
- 익명 함수
- Open EntityManager In View
- Batch
- 빈
- @FunctionalInterface
- open session in view
- OSIV
- Request flow
- 싱글 스레드
- Spring Framework
- 메서드 주입
- @Configuration
- 생성자 주입
- @componentScan
- 컴포넌트스캔
- mavenCentral
- 스프링 빈
- @Bean
- Handler Adepter
- open-in-view
- Today
- Total
보다 더 나은 내일의 나를 위해
DI[Dependency Injection] - @Autowired와 생성자 주입의 차이점 본문
개요
스프링은 클래스 간의 의존성을 풀기 위해 의존성 주입을 사용합니다.
의존성 주입을 할 때 크게 2가지 방법이 있습니다.
- @Autowired 어노테이션 활용
- 생성자 주입
위 두 가지 방법 모두 의존성을 성공적으로 주입해 줍니다. 하지만 스프링 4.3부터는 2번째 방법인 생성자 주입 방법을 권장합니다.
⚠︎ field injection is not recommended
그렇다면 이 둘의 차이점과 왜 생성자 주입 방법을 권장하는지 알아봅시다.
생성자 주입
사실 생성자 주입 방법을 사용한다고 해도 @Autowired 어노테이션이 사용됩니다.
만약 @Autowired를 생성자에 붙여주지 않고, 단일 생성자만 존재한다면 어노테이션이 사용되지 않아도 항상 사용됩니다. 또한 생성자가 많다면 가장 많은 필드에 의존성을 주입하는 생성자에 사용됩니다. 그리고, 이 생성자는 public이 아니어도 됩니다.
public class MyComponent {
private final MyRepository myRepository;
public MyComponent(MyRepository myrepository) {
this.myRepository = myRepository;
}
}
@Autowired
스프링에서는 의존성 주입을 손쉽게 해주는 어노테이션인 @Autowired를 제공합니다. 이 어노테이션은 사용하는 방법이 다양하게 존재합니다. 기본적으로 필드에 붙여 사용할 수 있습니다. 또한 @Autowired를 활용한다고 하면 이 방법으로 많이 작성합니다.
public class MyClass {
@Autowired
private MyComponent myComponent;
}
이 방법은 config method가 호출되기 전에 빈을 생성한 직후 주입됩니다. 이 방법 역시 필드는 public일 필요가 없습니다.
두 번째로는 파라미터에 붙이는 방법입니다. 이 방법은 Spring Framework 5.0 버전 이상부터 추가되었는데, 이는 기술적인 선언일뿐 프레임워크의 대부분은 이 선언을 무시합니다. 이 방법은 테스트 모듈인 JUunit Jupiter에서 사용할 수 있습니다.
세번째는 메서드에 붙일 수 있습니다. 보통 이 방법은 세터에 사용합니다. 이때 메서드의 인수는 스프링 컨테이너에서 일치하는 빈을 자동으로 연결시켜 줍니다. 또한 이 방법 역시 public일 필요가 없습니다.
public class MyService {
private MyRepository myRepository;
@Autowired
public void setMyRepository(MyRepository myRepository) {
this.myRepository = myRepository;
}
}
이 외에도 사용법이 많지만 이번엔 여기까지 알아보도록 하겠습니다.
필드 주입과 생성자 주입의 차이점
이 두 방법의 가장 큰 차이점으로는 의존성이 주입되는 타이밍에 있습니다. 만약 필드 @Autowired를 활용해 필드 주입방법을 선택한다면 Spring은 우선 객체를 빈에 등록합니다. 이후 필드에 맞는 빈을 찾아 주입하도록 동작합니다. 하지만 생성자 주입은 반대로 동작합니다. 객체를 빈에 등록하기 위해 생성자를 호출하는데, 이때 생성자의 인자를 먼저 스프링 컨테이너에서 빈을 찾아 주입합니다. 이 빈 주입이 다 되면 비로소 객체가 빈에 등록됩니다.
생성자 주입이 뭐가 좋다고 권장할까
그렇다면 생성자 주입을 권장하는 이유는 무엇일까요?
첫번째로는 순환 참조를 사전에 방지할 수 있습니다.
@Component
public class MyComponent {
@Autowired
private AnotherComponent anotherComponent;
}
@Component
public class AnotherComponent {
@Autowired
private MyComponent myComponent;
}
위 두 컴포넌트는 서로를 순환 참조하고 있습니다. 이때 실행시키면 어떻게 될까요?
매우 잘 실행됩니다. 하지만 순환 참조하고 있는 컴포넌트를 사용하는 순간 에러가 나게 됩니다. 이는 위에서 말했듯 필드 주입을 사용했을 때 객체를 빈에 먼저 등록시키고 의존성을 주입하기 때문에 발생할 수 있는 상황입니다.
@Component
public class MyComponent {
private final AnotherComponent anotherComponent;
public MyComponent(AnotherComponent anotherComponent) {
this.anotherComponent = anotherComponent;
}
}
@Component
public class AnotherComponent {
private final MyComponent myComponent;
public AnotherComponent(MyComponent myComponent) {
this.myComponent = myComponent;
}
}
이번에는 생성자 주입 방법입니다. 이렇게 실행시키면 스프링은 에러를 보기 좋게 뱉어냅니다.
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| anotherComponent defined in file [~~~/AnotherComponent.class]
↑ ↓
| myComponent defined in file [~~~/MyComponent.class]
└─────┘
덕분에 런타임중 치명적인 에러가 나는 것을 사전에 방지할 수 있게 됩니다.
참고
Spring Boot 2.6버전 부터는 필드 주입방식을 사용해 순환 참조가 일어나도 기본적으로 막아줍니다.
만약 이전 버전과 같이 실행시점에서 에러를 보지 말고 실행시키고 싶다면 spring.main.allow-circular-references를 ture로 지정해주면 됩니다.
두번째 권장 이유는 테스트 코드 작성에 용이합니다.
객체를 일단 빈에 등록하고 의존성을 주입하는 방법이 아닌 객체 생성 시점에 의존성을 모두 주입하게 되기 때문입니다.
세 번째로는 싱글톤 패턴인 빈 객체를 final로 선언할 수 있기 때문입니다.
빈 객체를 처음 할당 후 재 할당할 일이 많이 없으므로 보통 final로 선언합니다. 하지만 필드 주입이나 메서드 주입 등의 방법을 사용하면 final을 사용하지 못하거나 굳이 그 방법을 사용하는 이유가 사라지기 때문에 final로 선언할 수 있는 생성자 주입 방법을 사용합니다.
또한 개인적으로 필드 주입방법을 사용하면 의존성 주입이 필요한 객체 모두에 @Autowired를 붙여야 하기 때문에 개인적으로 가동성이 떨어졌습니다. 하지만 생성자 주입 방법을 사용하면 필드가 많을 때 비교적 코드가 줄어들기 때문에 생성자 주입 방법을 선호합니다.
'spring' 카테고리의 다른 글
[Spring Boot] Interceptor와 Filter는 어떤 게 다를까? (0) | 2022.06.27 |
---|---|
@Service를 record 클래스로 작성해도 괜찮을까? (0) | 2022.06.20 |
[Spring Boot] @Component와 @Bean의 차이는? (0) | 2022.06.13 |
Spring MVC 요청 흐름 (0) | 2022.06.02 |