🌱 김영한님의 스프링 핵심 원리 - 기본편을 수강한 후 학습한 내용을 정리하고 기록하기 위해 작성하는 포스팅입니다.
스프링 생명주기는 크게 빈 생성 단계
와 의존관계 주입 단계
로 나뉘어진다.
스프링 컨테이너는 싱글톤 컨테이너라고도 불린다. 왜냐하면 스프링 컨테이너에서 생성되고 관리되는 빈들은 기본적으로 싱글톤
(Singleton)으로 관리되기 때문이다. 이렇게 싱글톤으로 객체를 관리해주는 이유는, 객체를 생성하는 것에 비해 이미 생성된 객체를 가져오는 비용이 훨씬 적고, 이렇게 해주지 않으면 JVM 속에 같은 객체가 수없이 많이 생성되는 비효율이 발생하기 때문이다.
싱글톤이란, 실행되는 어플리케이션의 JVM 안에서 특정 객체가 하나만 존재함을 보장해주는 것이다. 물론 스프링이 싱글톤 방식으로만 빈을 생성하고 관리해주는 것은 아니고, 요청을 할 때마다 새로운 객체를 생성해서 반환하는 경우도 있는데, 이는 다른 포스팅에서 살펴볼 예정이다.
한편, 스프링 컨테이너를 사용하지 않고 직접 싱글톤 패턴을 구현할 경우에는 1) 구체클래스.getInstance()를 해줘야해서 구체 클래스에 의존하게 되고, 2) 유연한 테스트가 어려우며, 3) private 생성자를 쓰므로 자식 클래스를 만들기가 어렵다는 문제가 발생한다. 따라서 스프링은 이런 싱글톤 패턴 구현의 한계를 극복하고, 사용자는 이미 생성된 싱글톤 객체를 가져다쓰기만 하면 된다는 큰 장점을 지닌다.
스프링 빈은 싱글톤 빈인 경우가 많은데, 이때는 객체를 무상태로 설계해주는 것이 좋다. 여러 쓰레드가 같은 객체를 공유하다보면 공유 영역에서 문제가 발생하기 쉽기 때문이다.
의존관계 주입 방식은 이전 포스팅에서도 설명했듯이 크게 3가지 방식이 존재한다.
1) 생성자 주입
2) 수정자 주입
3) 필드 주입
생성자 주입이 좋은 이유
1번 방식이 여러모로 좋다는 것은 이전 포스팅에서도 설명했지만, 새롭게 배운 내용을 토대로 다시 한 번 생각해보자.
스프링 생명주기는 빈 생성 단계와 의존관계 주입 단계로 나뉘어지는데, 생성자 주입을 할 경우에는 빈을 생성할 때 어쩔 수 없이 의존관계 주입까지 같이 일어나게 된다. 따라서 순환 참조 관계의 빈은 컴파일 오류가 나게 된다.
또한, 생성자 주입 방식을 사용할 경우, 필요에 따라 테스트 코드에서 스프링 프레임워크의 도움없이도 다른 의존관계를 주입해볼 수 있다는 장점이 있다. 3번 필드 주입 방식의 경우 스프링없이 순수한 자바만으로는 테스트가 불가능한 것과는 대조되는 장점이다.
마지막으로, 생성자 주입을 할 경우 2, 3번 주입 방식과는 다르게 필드를 final로 설정해줄 수 있다. 즉 값을 변경할 필요가 없을 때에는 생성자 주입 방식이 단연 최적의 선택이다.
수정자 주입 방식을 쓰는 상황
특정 필드의 DI가 선택적이거나, 중간에 새로운 의존관계를 주입해주는 식으로 변경이 필요할 때에는 수정자 주입 방식도 종종 사용된다.
필드 주입 방식을 쓰는 상황
필드 주입 방식은 안티 패턴이므로 사용하지 않는 것이 좋다. 그러나 코드가 간결하기 때문에(사실 Lombok을 쓰면 생성자 주입 방식도 코드가 매우 깔끔해서 이 장점은 더 이상 적용되지 않는다..ㅎ) 테스트 코드 자체, 혹은 Configuration 클래스에서 특수한 목적으로 간혹 쓰이기도 한다.
정말 당연한 이야기지만 스프링 컨테이너가 관리하는 스프링 빈에 한에서만 @Autowired가 동작한다. 스프링 컨테이너 속에서 빈을 찾아서 꺼내온 후 이를 주입해주는 것이다.
@Autowired(required = false)로 설정하면 의존 관계 주입이 필수가 아니게 된다. 이렇게 하면 의존관계를 주입하는 setter 메서드는 호출 자체가 안된다.
@Nullable이면 null이 주입되고, @Optional이면 Optional.empty가 주입된다.
@Test
void AutowiredOption(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
}
static class TestBean {
@Autowired(required = false)
public void setNoBean1(Member noBean1){
System.out.println("noBean1 = " + noBean1);
}
@Autowired
public void setNoBean2(@Nullable Member noBean2){
System.out.println("noBean2 = " + noBean2);
}
@Autowired
public void setNoBean3(Optional<Member> noBean3){
System.out.println("noBean3 = " + noBean3);
}
}