- @Configuration 와 싱글톤2023년 07월 13일
- starryeye
- 작성자
- 2023.07.13.:04
@Configuration 은 스프링 컨테이너에 스프링 빈을 등록할 수 있도록 해주는 어노테이션이다.
@Configuration 이 적용된 클래스 내부에
다수의 @Bean 이 적용된 메서드가 있으면 스프링 빈으로 등록해준다.
@Bean 이 적용된 메서드가 하는 역할은
반환되는 객체를 해당 메서드의 이름을 가진 스프링 빈으로 등록한다.
<참고>
@Bean 은 스프링 빈으로 등록되는 클래스에 작성하여도 동작한다.
(@Component 적용된 클래스 내부에 작성)
본격적으로 이번 포스팅 주제인..
@Configuration 어노테이션의 이해도를 높여보자.
@Configuration public class AppConfig { @Bean public MyBean myBean() { return new MyBean(); } @Bean public MyOtherBean myOtherBean() { return new MyOtherBean(myBean()); } @Bean public MyAnotherBean myAnotherBean() { return new MyAnotherBean(myBean()); } }
위와 같은 코드가 있다.
myOtherBean 메서드에서 해당 클래스의 내부 메서드인 myBean() 을 호출하고 있다..
Spring 에서 @Configuration, @Bean 을 사용하던..
@Component 를 사용하던 Bean Scope 를 기본으로 두면 싱글톤이다.
하지만, 코드를 보면 분명히
MyOtherBean 생성자와 MyAnotherBean 생성자에서
각각 myBean() 메서드를 호출을 하고 있는 것을 볼 수 있다.
그래서 코드만 해석하자면 최종적으로 스프링 빈 등록 시...
myOtherBean 이름의 bean 과 myAnotherBean 이름의 bean 에
서로 다른 MyBean 객체가 주입되는것 처럼 보인다.
그러면.. 스프링 컨테이너가 MyBean 객체를 싱글톤으로 관리한다는 말에 위배된다.
그리고 myBean 메서드는 로그를 찍어보면 1회만 호출된다...
이것을 어떻게 가능하게 하는지 알아보겠다.
@Configuration 어노테이션을 뜯어보면,
내부에 proxyBeanMethods 라는 요소가 있는데 이 값은 기본이 true 이다.
해당 요소가 true 이면..
@Configuration 을 적용한 객체(AppConfig)를 빈으로 등록할 때 (@Configuration 도 @Component 를 내장하고 있다.)
CGlib 을 사용한 동적 프록시 객체로 등록하게된다.
따라서 스프링 컨테이너에 빈을 등록하기 위해
@Bean 어노테이션이 붙은 빈을 생성하고 반환해주는 메서드가 호출되기 전후로
무엇인가를 해줄 수 있게 되는 것이다.
<참고>
실제로 proxyBeanMethods 요소의 값을 false 로 주면 서로 다른 MyBean 객체가 주입된다.
그리고 AppConfig 객체가 스프링 빈으로 등록될 때,
동적으로 생성된 프록시 객체가 아닌 원본 객체가 빈으로 등록된다.
그 무엇인가는 알고 나면 간단하다.
내부적으로 myBean 이름을 가진 빈이 아직 생성되지 않았다면 생성하고
내부적으로 myBean 이름을 가진 빈이 생성되어 있으면 해당 객체를 그대로 사용한다.
(내부적으로 맴버 변수로.. 캐시 성격의 key-value 컨테이너 사용 추측..)
-> 즉, 이미 생성되어 있다면 원본 객체의 myBean() 메서드를 호출 하지 않는다. 그래서 싱글톤을 보장할 수 있는 것이다.
여기서 좀 더 생각해보자..
스프링 AOP 를 배웠다면 위화감이 들 수도 있다.
스프링 AOP 에서는 내부 메서드를 호출하면 AOP 가 적용되지 않는다고 알고있다.
this.myBean() 으로 직접호출이 되어버리기 때문이다.
그래서 아래와 같이 생각해볼 수 있다.
내부 메서드를 호출 하고 있기 때문에 AOP 가 적용되지 않으므로..
포인트컷에 의한 어드바이스가 동작하지 않는다.
즉..
myBean() 은 내부 메서드 이므로 뭔가 프록시 처리가 안되고 제대로 동작이 잘 안될 것만 같다..
실험을 해본 결과..
AppConfig 는 원본 객체를 호출 하지 않는다..
-> @Bean 이 적용된 메서드 내부에서 this 로 로그를 남겨보면.. 원본객체가 아닌 CGlib 객체로 나온다..
즉, CGlib 를 사용하여 원본 객체를 상속하여 객체를 만들었지만 원본 객체를 따로 만들고 원본 객체를 호출 안함
그래서 내부 호출을 하더라도 CGlib 에 의해 만들어진 객체에서 그대로 호출되는 것으로 생각된다.
<실제로 확인>
기본적으로 @Transactional 적용된 객체는 프록시 객체(by CGlib)와 원본 타겟 객체가 서로 분리 되어있다.
프록시 객체에서 원본 객체로의 참조 관계가 명확하게 존재한다.
하지만, @Configuration 의 경우엔.. 좀 다르다.
상속관계를 이용한 참조? 로 원본객체를 대신한다....
따라서 따로 생성된 원본객체가 없고 프록시만 존재하는데
원본 객체를 대신하여 super 를 이용한다고 확인 되었다.
-> 확인 가능 소스코드 경로
org.springframework.context.annotation.ConfigurationClassEnhancer.java 파일에서
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback 클래스의
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) 메서드에
주석과 코드를 보면 이해가 될 것이다.
한번도 생성안하면 invokeSuper, 생성해놨다면 캐싱되어있는 저장소에서 꺼내서 사용
마지막으로 실험한 코드와 그에 따른 로그를 보여주고 마무리 하겠다.
proxyBeanMethods=true 인 경우..
예상한대로.. 모든 빈이 싱글톤으로 유지되고 있음을 알 수 있다.
MyBean 생성자와 myBean 메서드는 1회 호출 되었음을 알 수 있다.
또한, AppConfig 내부 메서드에서 찍은 this 에 CGlib 로 나타난다...
-> 글쓴이가 원본 객체의 메서드를 호출하지 않는 것 같다고 생각한 이유..
proxyBeanMethods=false 인 경우..
AppConfig 는 CGlib 로 생성되지 않았음을 알 수 있다. (원본 객체)
myBean 메서드가 호출이 중복적으로 나타나고 의존하는 객체에 서로다른 인스턴스가 주입되고 있다.
또 눈여겨 보면 좋은 점은 최초로 생성된 객체가 스프링 빈으로 등록되고.. 해당 스프링 빈은 싱글톤이다.
-> 지금 논점은 @Configuration 내부에서 싱글톤을 유지하냐 마냐이다.
-> 당연히 스프링 컨테이너에 등록된 스프링 빈은 싱글톤이다.
'Spring > Core' 카테고리의 다른 글
Spring 은 SOLID 원칙을 잘 지키도록 도와준다. (1) 2023.11.14 @Async, TaskExecutor, CompletableFuture (0) 2023.06.17 Spring AOP 정의 (0) 2022.11.29 Spring에서 프록시 사용 6 (0) 2022.11.29 Spring에서 프록시 사용 5 (0) 2022.11.20 다음글이전글이전 글이 없습니다.댓글