앞선 포스팅에서 컴포넌트 스캔을 통해 의존관계를 자동으로 주입하는 법에 대해 알아보았다.
https://ohreallystore.tistory.com/43
[WEB] 스프링 컴포넌트 스캔
이전까지는 스프링 빈을 등록하기 위해서는 @Bean 애노테이션을 통해 직접 작성해야 했다. @Bean public Bean bean() { return new Bean(); } 등록할 스프링 빈의 개수가 많지 않다면 상관이 없겠지만, 스프링
ohreallystore.tistory.com
이번 포스팅에서는 다양한 의존관계 주입 방법에 대해 알아보고, 어떻게, 또 어떤 의존관계 주입 방식을 사용해야 하는지에 대해 알아보자.
이 포스팅은 김영한님의 '스프링 핵심강의 - 기본편' 강의에 의존하고 있습니다.
다양한 의존관계 주입 방법
의존관계를 주입하는 방법은 크게 4가지가 있다.
- 생성자 주입
- Setter (수정자) 주입
- 필드 주입
- 일반 메서드 주입
생성자(Constructor) 주입
이름 그대로 생성자를 호출해 의존관계를 주입하는 방법이다. 생성자는 딱 한번, 반드시 실행되어야 한다는 특징이 있어, 생성자 주입을 이용하면 불변, 필수 의존관계가 형성된다.
public class ClassA {
private final int x;
@Autowired
public ClassA classA(int x) {
this.x = x;
}
}
생성자 주입시에는 보통 의존관계가 주입될 필드 값에 final 키워드를 붙이게 되는데, final 키워드를 붙이면 해당 필드는 불변해야 하고, 반드시 초기화 해야 한다. 이는 생성자 주입의 불변, 필수 특성과 동일하다. 즉, final 키워드를 통해 컴파일 단계에서 생성자 주입시 일어나는 오류를 파악할 수 있기 때문에 final 키워드를 관례적으로 사용한다.
만약 다음과 같이 실수로 생성자 블록에 필드값을 초기화 하지 않았다고 생각해보자. 그러면 Final 키워드가 붙은 필드 값을 초기화가 되지 않았으므로 컴파일 단계에서 오류를 일으킨다.
어떠한 장애나 오류가 발생시 왠만하면 컴파일 단계에서 발견하고 해결하는 것이 중요하다. 런타임 단계에서 오류가 발생하면 중대한 장애나 문제를 일으키기 쉽고, 파악이 어렵다. 개발중 언제나 실수는 할 수 있기 때문에 그 실수를 빨리 발견하고 해결하는 것이 중요하다.
Setter (수정자) 주입
Setter 메서드를 호출해 의존관계를 주입하는 방법이다. Setter 메서드를 필드값을 수정할때 호출하는 메서드로 2번 이상 호출이 가능하고, 필요한 경우에만 호출할 수 있다. 이러한 특성으로 인해 Setter 주입은 선택, 변경 가능성이 있는 의존관계에 사용해야 한다.
public class ClassA {
private final int x;
@Autowired
public ClassA classA(int x) {
this.x = x;
}
@Autowired
public void setX(int x) {
this.x = x;
}
}
다음과 같이 Setter 메서드를 선언해 사용한다.
주의) @Autowired는 기본적으로 주입할 대상이 존재하지 않을 경우, 오류를 발생시킨다. 그러므로 만약 주입 대상이 없어도 동작하게 만들려면 @Autowired(requied=false)로 지정해야 한다. (뒤에 다룰 예정)
필드(field) 주입
말그대로 필드에 바로 주입하는 방법이다.
public class ClassA {
@Autowired private int x;
}
코드가 매우 간결해 좋아보이지만, 외부에서는 값을 수정할 수 없다는 치명적인 문제가 발생한다. 이는 테스트를 작성하기 매우 어렵다는 단점을 가지고 있다.
쉽게 말해 쓰지 말라는 이야기이다.
일반 메서드 주입
일반적인 메서드를 통해 의존관계를 주입하는 방법이다. 한번에 여러 대상의 의존관계를 주입할 수 있고, 일반 메서드 특성상 유연하게 사용이 가능하다. 하지만 위에 설명한 주입 방식 만으로 대부분의 경우를 커버할 수 있어 일반 메서드 주입은 잘 사용하지 않는다.
주입할 스프링 빈이 없다면?
상황에 따라 주입할 스프링 빈이 없을 수 있다. 이 경우 @Autowired 만 지정할 경우, 오류를 발생시킨다. 왜냐하면 @Autowired 애노테이션의 required 기본 값이 true로 설정되어 있기 때문이다.
주입할 스프링 빈이 없다면 어떻게 처리하는게 좋을까? 크게 2가지 방법을 생각할 수 있을 것이다.
- 메서드 자체를 호출하지 않는다. => @Autowired(required = false)
- null 값을 리턴한다 => @Nullable / Optional<>
@Autowired(required = false)
@Autowired(required = false)로 지정하게 되면 주입할 대상이 없을 경우 해당 의존관계 주입 메서드를 아예 호출하지 않는다.
@Autowired(required = false)
public void setX(User x) {
this.x = x;
}
만약 x값이 빈에 등록되지 않았다면, setX는 호출되지 않는다.
@Nullable
@Nullable을 주입할 대상에 지정하면 주입할 대상이 없을 경우, null이 입력된다.
@Autowired
public void setX(@Nullable User x) {
this.x = x;
}
Optional<>
Optional<>은 스프링이 아닌 자바8 이후 제공하는 API 중 하나로 값이 있으면 해당 값을 할당하고, 없을 경우 Optional.empty(비어 있는 값을 리턴함)를 호출한다.
@Autowired
public void setX(Optional<User> User x) {
this.x = x;
}
Optional은 자바8에서 매우 중요하게 다뤄지는 개념으로 추후 포스팅해보겠다.
생성자 주입을 사용해야 하는 이유
위에서 다양한 주입 방식에 대해 알아보았다. 그렇다면 실무 개발에서는 어떤 방식을 사용해야 할까? 답은 묻지도 따지지도 않고 그냥 생성자 주입을 사용하면 된다. 지금부터 그 이유를 알아보자.
대부분의 의존관계는 변경되지 않는다. 오히려 변경되면 큰일난다.
대부분의 의존관계는 어플리케이션의 종료시까지 변경되면 안된다. 만약 Setter 주입을 사용하면 Setter 메서드는 public으로 열려있으므로, 실수로 다른 곳에 변경할 여지가 충분히 있다. 하지만 생성자는 한번 실행되면 다시 실행이 불가하므로, 의존관계를 여러번 주입하여 의존관계가 변경되는 불상사를 방지할 수 있다.
누락되거나 잘못 작성 됐을시, 컴파일 단계에서 발견하기 쉽다 (feat. fianl 키워드)
위에서도 언급했지만 생성자 주입을 할 경우, 주입 대상의 필드에 final 키워드를 지정할 수 있다. final 키워드는 해당값이 변하거나 초기화 되지 않았을 경우, 컴파일 단계에서 오류를 발생시킨다. 이를 통해 잘못된 생성자 주입 (일부 필드를 누락했다거나, 또는 중복해서 주입 시)을 컴파일 단계에서 잡아낼 수 있다.
다시 말하지만 세상에서 제일 좋은 오류는 컴파일 단계에서 발생한 오류이다.
마무리
다양한 의존관계 주입에 대해 알아보았고, 주입 대상이 없는 예외적인 상황을 어떻게 처리할지에 대해서도 알아보았다. 다양한 이야기를 했지만 돌고 돌아 결국 생성자 주입을 사용하는 것이 대부분의 상황에서는 불필요한 오류를 막고, 유지보수를 쉽게 만들어준다.
의존관계 주입에 대해 알아보면서 깨달은 점이 있다면 단순히 오류가 없는 프로그램을 짜는 것이 중요한게 아니라, 오류를 쉽게 파악하고 보수할 수 있는 프로그램을 짜는 것이 더 중요한 것일지도 모른다는 점이다.
'WEB' 카테고리의 다른 글
[WEB] 빈 스코프 1 - 프로토타입 스코프 (Prototype Scope) (0) | 2022.05.20 |
---|---|
[WEB] 빈 생명주기 콜백 (0) | 2022.05.10 |
[WEB] 스프링 컴포넌트 스캔 (0) | 2022.04.28 |
[WEB] 스프링 컨테이너와 싱글톤 패턴 (0) | 2022.04.22 |
[WEB] 스프링 컨테이너 (Spring Container) (0) | 2022.04.19 |