Published 2023. 5. 12. 23:30
들어가며
- 이전까지 스프링 컨테이너에서 어떻게 각 객체를 싱글톤 기법으로 관리하고 있는지에 대해 알아보았다.
- 이번에는 @Configuration으로 등록한 설정 파일이 실행될 때 생기는 현상 중, 객체가 여러번 생성되는 것 처럼 보이는 상황과 그에 대해 실제로 어떤 동작이 수행되고 있는지에 대해 알아본다.
@Configuration
- 다음의 AppConfig 코드를 보자.
@Configuration
public class AppConfig{
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
}
- 이전에 스프링은 앱이 실행되면 @Configuration이 적힌 설정 파일을 이용하여 @Bean이 들어간 모든 메서드들을 실행시켜 그 객체를 반환받아 관리한다고 하였다.
- 동시에 스프링 컨테이너는 싱글톤으로 관리되고 있기 때문에 모든 객체들은 단 하나여야만 한다.
- 그런데 잘 생각해보면 다음과 같은 상황이 일어날 수 있다.
- 코드가 위에서 아래로 수행된다고 가정할 때 가장 먼저 수행되는 메서드는 memberService()일 것이다.
- memberService()메서드는 memberRepository()메서드를 실행시켜 memberRepository의 구현체를 주입받고, 서비스 구현체를 생성하여 반환한다.
- 이때 memberRepository가 인스턴스화 하여 반환된 횟수는 1번이다.
- 그 다음 orderService()가 수행되고 역시 memberRepository와 discountPolicy 구현체를 주입받아 자신의 orderService 구현체를 만들어 반환한다.
- 여기서부터 무언가 이상하다. memberRepository() 메서드 에서 new를 통해 memberRepository 객체를 생성하여 반환하고 있는데 지금이 2번째 수행된 것이므로 새로운 객체가 2번 만들어진다.
- 이후에 memberRepository()도 @Bean으로 등록했기 때문에 다시 수행되며 총 3번이 수행된다. 즉 총 3개의 새로운 객체가 생성되어 반환되는 것으로 보인다.
- discountPolicy() 역시 2번 수행되며 2개의 새로운 객체가 생성되어 반환되는 것으로 보인다.
- 이것은 스프링 컨테이너가 각 객체를 싱글톤으로 관리한다는 사실과 정면으로 부딪히는 주장이다. 그런데 검증을 해보면 정상적으로 스프링 컨테이너가 각 객체를 싱글톤으로 관리한다는 결과가 나온다.
바이트코드 조작
- 왜 이런 상황이 발생함에도 불구하고 싱글톤으로 관리하는게 가능한지는 @Configuration이 붙은 AppConfig의 클래스 정보를 확인하면 알 수 있다.
- 다음과 같은 코드를 통해 AppConfig의 클래스 정보를 확인해보자.
public class Example{
public static void main(String[] args){
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean = " + bean.getClass());
}
}
- AnnotationConfigApplicationContext에 매개변수로 넘겨진 값도 스프링 Bean으로 등록된다. 그래서 지금 넘어간 AppConfig도 Bean으로 가져올 수 있다.
- 이제 이 Bean을 조회해서 클래스 정보를 알아보자. 우리가 생각하는 것이 맞다면 class.hello.core.AppConfig와 같이 출력되어야 한다.
bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$bd479d70
그런데 출력된 값이 다르다. 뒤에 어떤 값이 추가적으로 붙어있다.
- 이것은 스프링에서 해당 클래스를 CGLIB이라는 바이트코드 조작 라이브러리를 통해 AppConfig 클래스를 상속받은 임의의 클래스를 만들어 그 클래스를 스프링 빈으로 등록한 것이다.
- 즉 실제 AppConfig 클래스는 스프링 빈으로 등록되어있지 않다는 것이다.
- AppConfig를 상속받은 클래스는 다음과 같이 구현되어있을 것으로 추측된다.
@Bean
public MemberRepository memberRepository(){
if(memoryMemberRepository 가 스프링 컨테이너에 존재한다면){
return 스프링 컨테이너에서 해당 객체 찾아서 반환
} else{
기존 로직을 호출하여 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
return 생성된 객체;
}
}
- 즉 해당 객체가 이미 스프링 컨테이너에 존재하는지를 먼저 확인하고, 존재하지 않는 것이 확인되면 새로운 객체를 생성하여 컨테이너에 등록하고 해당 객체를 반환한다.
- 만약 이미 존재한다면 해당 객체를 스프링 컨테이너에서 찾아서 반환한다.
- 이렇게 보이지 않는 작용을 통해서 스프링 컨테이너는 싱글톤으로 관리하는 것을 실패하지 않을 수 있다.
- 이 작용은 @Configuration이 붙은 클래스에 대해 적용되고 있기 때문에 이 어노테이션을 사용하지 않는다면 바이트코드 조작이 일어나지 않은 클래스가 등록되게 되고 위의 필터를 거치지 않게 되어 중복이 일어난다. 즉 싱글톤으로 관리되는것이 보장되지 않는다.
- 따라서 스프링 컨테이너가 싱글톤으로 객체를 관리하기 위해서는 항상 @Configuration을 사용하여 설정 파일을 관리해야한다.
'Spring & JPA > Spring' 카테고리의 다른 글
Spring Framework - 컴포넌트 스캔 - 2. 컴포넌트 스캔 위치, 대상, 필터 (0) | 2023.05.15 |
---|---|
Spring Framework - 컴포넌트 스캔 - 1. 컴포넌트 스캔과 의존관계 자동 주입 (0) | 2023.05.13 |
Spring Framework - 스프링과 Singleton - 2. 싱글톤 컨테이너 (0) | 2023.05.10 |
Spring Framework - 스프링과 Singleton - 1. Singleton (0) | 2023.05.09 |
Spring Framework - Spring Bean 조회 (1) | 2023.05.08 |