자동, 수동 의존 관계 주입을 배우면서 차이점이 정확히 뭐고, 어떤 상황에 쓰이는 지 이해가 부족한 것 같아서 추가로 정리하고자 해당 포스팅을 쓰게 되었다.
스프링 컨테이너에 빈을 등록하는 대표적인 방식은 두 가지가 있다.
여기서 말하는 수동이 뭐고, 자동이 뭔지 알아가보겠다.
이는 조금만 생각하면 당연하다는 거를 알 수 있다.
우리가 어떤 변수를 선언한다고 해보자
int num;
그러면 처음엔 자동으로 의미없는 쓰레기값이 들어가게 될 것이다.
하지만, 값을 초기화하면 이후에 계속 그 값으로 고정된다.
비슷한 원리로 생각했을 때 스프링에서 수동 빈이 자동 빈보다 우선적으로 적용된다는 사실을 쉽게 이해할 수 있을 것이다.
코드로 수동 빈 등록을 알아보자
@Configuration
public class AppConfig {
//@Bean을 붙힌 메서드는 전부 스프링 컨테이너에서 관리된다.
@Bean
public MemberService memberService() {
System.out.println("AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
//역할 : 저장소
//구현체 : 메모리 저장소
@Bean
public MemberRepository memberRepository() {
System.out.println("AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
System.out.println("AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
// return null;
}
//역할과 구현 분리
//역할 : 할인 정책
//구현체 : 정액할인 정책
@Bean
public static DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
구조가 간단해 보이지만, 모르는 애노테이션들이 많이 보인다.
하지만, 우리는 여기서 딱 두 가지만 기억하면 된다.
"@Configuration
, @Bean
"
먼저, @Configuration
스프링 컨테이너에 의해 처리될 설정 클래스임을 나타낸다.
@Component
를 기반으로 동작하며, 스프링이
해당 클래스를 자동으로 감지하고 처리한다.
어려운 용어가 많은데 그냥 빈으로 등록할 메서드들을 가지고 있는 클래스라고 생각하면 된다.
그리고 여기서 @Component
를 기반으로 동작한다는 것은 아래 과정을 통해 이해할 수 있다.
@ComponentScan vs @Component
1. 우선적으로 빈으로 등록할 클래스에 @Component 애노테이션 을 선언
2. 이후, @ComponentScan을 통해 @Component을 사용한 클래스를 스캔하고, 이를 빈으로 등록
@ComponentScan이 탐색하는 범위
@SpringBootApplication
이 있는 클래스가core.web
패키지에 위치할 때,core.web
과 그 하위 패키지 모두를 포함해서 탐색한다.
이런 과정은 전부 @SpringBootApplication
이 자동으로 수행해준다.
(@SpringApplication
안에 @ComponentScan
이 있기 때문)
따라서, 우리는 스프링 애플리케이션을 실행하면 AppConfig
를 빈으로 등록해서 사용할 수 있다.
다음으로, @Bean
AppConfig
내부에서는 우리가 빈으로 설정해서 사용할 메서드를 정의한다. @Bean
애노테이션이다.테스트 환경에서 스프링 애플리케이션을 띄우지 않는 경우, ApplicationContext
를 통해서 초기화해줄 수 있다.
ApplicationContext
은 스프링의 핵심 코어로 빈 관리 및 설정 및 초기화, 의존성 주입 등 스프링과 관련된 핵심 로직을 수행한다.
스프링을 띄워주는 CoreApplication
에서는 기본적으로 ApplicationContext
를 초기화해주고, 빈 등록 및 의존 관계 설정을 모두 수행해준다.
하지만, 스프링을 띄우지 않는 테스트 환경에서는 이와 같은 작업을 수동으로 해주어야하기 때문에 아래와 같은 과정을 거치는 것이다.
@Test
...
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = ac.getBean("memberService", MemberService.class);
간단하게 정리하자면, 수동 빈 등록을 하기 위해서 거치는 과정은 다음과 같다.
@Configuration
애노테이션 붙히기@Bean
애노테이션 붙히기이후 스프링을 띄우냐 안 띄우냐에 따라서
ApplicationContext
를 선언할 수도 있고 안 할 수도 있다.
이번엔 자동 빈 등록에 대해서 알아보겠다.
수동 빈 등록 과정에서는 우리가 일일히 빈으로 등록할 메서드를 선언했어야 했다.
하지만, 자동 빈 등록은 이보다 더 편리하게 사용할 수 있다.
코드로 먼저 알아보자
MemoryMemberRepository.class
@Component
public class MemoryMemberRepository implements MemberRepository{
...
}
MemberServiceImpl.class
@Component
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
@Autowired //ac.getBean(MemberRepository.class)
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
모르는 애노테이션이 많이 등장하는데 자동 의존 관계도 딱 두 가지만 알면 된다.
"@Component
, @Autowired
"
@Component
는 어떤 애노테이션인지 알 것 같은데 그러면 @Autowired
는 뭘까?
@Autowired
:
의존성 주입
을 스프링 빈들 간 자동으로 주입해주는 것이다.여기서 의존성 주입은 의존관계를 설정해주는 것인데
코드로 이해해보자면
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
다음과 같은 코드에서 현재 인터페이스인 memberRepository
의 구현체로 어떠한 것도 선언되어 있지 않다.
이게 가능한 이유는
@Autowired
애노테이션이 붙은 메서드에 한해서
memberRepository
하나로 초기화해주더라도 컨테이너에 등록된 빈을 탐색해서 적절한 구현체를 넣어주기 때문이다.
이는 예전에 포스팅으로 올렸던
스프링 Controller 및 Mapping애노테이션 이해하기 에서 분석했던
list
메서드로 쉽게 이해할 수 있다.
@GetMapping("/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
해당 메서드에서 파라미터로 넘어오는 Model model
을 스프링이 참조값으로 넘겨줬듯이 MemberServiceImpl
에서도 마찬가지로 스프링이 파라미터로 MemberRepository
객체를 넘겨주게 된다.
이 때 넘겨주는 객체는 스프링 컨테이너에 등록된 빈 객체를 탐색해서 전달해준다.
실제로 스프링 컨테이너에 등록된 빈들은 다음과 같이 객체가 생성된 채로 등록되어 있기 때문에 빈 객체를 넘겨줄 수 있는 것이다.
스프링 애플리케이션을 띄우지 않는다면?
자동 빈은 설정 클래스가 필요 없지만, 스프링 애플리케이션을 띄우지 않는다면, @ComponentScan
을 대신 해주는 설정 클래스가 존재해야 한다.
따라서, 스프링을 띄우지 않는 테스트 환경에서 테스트를 진행하기 위해 아래와 같은 클래스를 선언해주었다.
AutoAppConfig.class
@ComponentScan
public class AutoAppConfig {
}
이제 테스트 환경에서 이 설정 클래스를 가지고
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
다음과 같이 선언해서 자동 빈 등록을 진행할 수 있는 것이다.
이에 대해 애플리케이션 설계 관점으로 바라보았을 때 두 가지로 나뉘는데
업무 로직 관점에서는 에러가 발생했을 때 어디서 발생했는 지 쉽게 찾을 수 있다.
왜냐하면, 기본적인 서비스나 레포지토리 등 일반적인 형태로 사용되는 경우 에러가 명확하기 때문이다.
따라서, 자동 빈 등록을 통해 간결하게 작성하는 게 효율적이다.
반면에, 기술 지원 로직 관점에서는
오류가 발생했을 때 어디서 발생했는 지 찾기가 매우 어렵다.
기술 지원 빈같은 경우 업무 로직과 비교해서 그 수가 매우 적은데 반해 애플리케이션에 광범위한 영향을 미치기 때문이다.
따라서, 이 경우 수동 빈 등록으로 해주는 게 관리하는 데에 용이하다.
보통 실무에서는 상황에 따라 적절하게 조합해서 사용하는 게 일반적인 형태이다.
수동 빈 등록은 설정을 더 세밀하게 할 수 있고, 자동 빈 등록은 코드가 간결하고 쉽다.
따라서, 각각의 특성에 맞게 적절하게 조합해주는 게 유지보수하기에도 편하다.