스프링 강의를 수강하던 중 싱글톤 컨테이너와 관련해서 테스트를 진행하였다.
스프링 컨테이너는 싱글톤 컨테이너이기 때문에 반드시 동일한 객체를 반환해야 하는데 서로 다른 객체가 반환되었다.
![[Spring] static 메서드에 @Bean을 적용하였을 때 feat.프록시(Proxy) [Spring] static 메서드에 @Bean을 적용하였을 때 feat.프록시(Proxy)](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
// Test 코드
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
System.out.println("memberRepository1 = " + memberRepository1);
System.out.println("memberRepository2 = " + memberRepository2);
Assertions.assertThat(memberRepository1).isSameAs(memberRepository2);
//AppConfig.class
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public static MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(),
discountPolicy());
}
@Bean
public static DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
이유가 궁금해서 찾아보니 @Bean 메서드에 static을 작성하였기 때문이라는 것을 알게 되었다.
@Bean과 static은 무슨 관계이길레 싱글톤이 적용되지 않는걸까??
먼저 Spring 공식 문서를 보자.
By marking this method as static, it can be invoked without causing instantiation of its declaring @Configuration class, thus avoiding the above-mentioned lifecycle conflicts. Note however that static @Bean methods will not be enhanced for scoping and AOP semantics as mentioned above.
이 메서드를 정적으로 표시함으로써 @Configuration 클래스의 선언을 인스턴스화하지 않고 시작할 수 있으므로 위 라이프사이클 충돌을 피할 수 있습니다. 단, 전술한 바와 같이 스태틱 @Bean 메서드는 스코프 및 AOP 시맨틱스에 대해 확장되지 않습니다.
영어가 부족해서인지 스프링 이해가 부족해서인지 무슨 소리인지 잘 모르겠다.
일단은 @Bean 메서드에 static을 적용하면 인스턴스화하지 않는다는 것을 보아하니 스프링 컨테이너에 실리지 않는다는 것이고, 스프링 컨테이너에 실려있지 않기 때문에 호출할 때마다 새로운 객체를 만들어지는 것으로 생각되는데 아직도 명확하게 이해가 되지 않는다.
좀 더 알아보니 스택오버플로우에 나와 똑같은 의문을 가진 사람이 있었다.
https://stackoverflow.com/questions/30874244/bean-annotation-on-a-static-method
@Bean annotation on a static method
Can anyone explain me why a @Bean on a static method is returning 2 different instances ? I can understand that @Bean on a method non static like the class A is returning the same instance because...
stackoverflow.com
스택오버플로우의 답변을 내 경우에 대입해보면 Test 코드에서 memberRepository 인스턴스를 꺼내기 위해
getter 메서드를 사용하였기 때문에 다른 객체가 나온 것이라고 한다.
![[Spring] static 메서드에 @Bean을 적용하였을 때 feat.프록시(Proxy) [Spring] static 메서드에 @Bean을 적용하였을 때 feat.프록시(Proxy)](https://blog.kakaocdn.net/dna/4Atme/btsak9AaN30/AAAAAAAAAAAAAAAAAAAAAAwQhO2k9iG35yJLrnVYKQzxUsxtVLjNoe_kWyDxDHy3/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1756652399&allow_ip=&allow_referer=&signature=Ce2%2B412Bv5J2%2BQ54vjIN5F2AUJ0%3D)
![[Spring] static 메서드에 @Bean을 적용하였을 때 feat.프록시(Proxy) [Spring] static 메서드에 @Bean을 적용하였을 때 feat.프록시(Proxy)](https://blog.kakaocdn.net/dna/cY5A1z/btsaJUBsK0v/AAAAAAAAAAAAAAAAAAAAAK0CY_P9OG3dSaXl5eEx9eBWj0_AWuypJYOMPqaq-2Lj/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1756652399&allow_ip=&allow_referer=&signature=w%2F6UTHxEA2n7pLnc4Ll0XxCfzRQ%3D)
위 사진의 @Configuration 이 적용된 AppConfig 클래스에 적힌 코드를 살펴보면 MemberService와 OrderService 모두 MemberRepository에 의존적인 것을 알 수 있다.
빈의 의존관계는 @Configuration 클래스를 위해 프록시가 생성되며, 메서드가 호출되었을 때 생성된 프록시가 메서드 호출을 가로채서 빈을 전달하며 이루어진다.
하지만 static 메서드는 프록시화 되지 않기 때문에 static 메서드를 작성하면 Spring은 이를 인식하지 못하고 일반 Java 객체를 가져오게 된다.
어느정도 윤곽이 잡히는 것 같다. 그럼 이제 프록시는 무엇인가.....?????
프록시(Proxy)란?
대리(행위)나 대리권, 대리 투표, 대리인 등을 뜻한다.
CS적인 의미로는 프로토콜 상에서 무엇인가를 대신하는 것을 뜻한다.
대리.. 무언가를 대신 해주는 것인데??
검색해보니 Java에는 Proxy Pattern이란게 존재했다.
Proxy는 RealSubject(실제 서비스에 필요한 코드)를 직접 호출하지 않고 Proxy(대리인)을 호출하여 Proxy로 하여금 RealSubject를 대신 호출시키는 디자인 패턴이다.
![[Spring] static 메서드에 @Bean을 적용하였을 때 feat.프록시(Proxy) [Spring] static 메서드에 @Bean을 적용하였을 때 feat.프록시(Proxy)](https://blog.kakaocdn.net/dna/chP3IN/btsaiaNopCH/AAAAAAAAAAAAAAAAAAAAAEbXF3vz6F8hpLvZgRzWlLkvHMlFbYF-6Gm4tpSVC4DP/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1756652399&allow_ip=&allow_referer=&signature=SaJ4jqHwHu%2B00Kj%2Fkk6nvso2ifQ%3D)
Subject 인터페이스의 request() 메서드를 호출하면 RealSubject의 requset()가 호출된다.
이 때 Proxy 클래스가 대신 RealSubject의 request() 메서드를 호출하고 그 반환값을 클라이언트에 전달하는 방식이다.
즉, 인터페이스를 구현한 클래스에 직접적으로 접근하지 않고 Proxy 클래스를 통해 한 번 우회하여서 흐름을 제어하는 것이며, 클라이언트는 반환된 값이 RealSubject 의 반환값인지 Proxy의 반환값인지 알 수 없다.
결론
위 그림을 토대로 Proxy 클래스와 RealSubject 클래스 모두 Subject 인터페이스를 구현하였고, request() 메서드를 오버라이딩해서 사용하고 있다.
즉 인터페이스를 구현하는 구현체와 Proxy 모두 오버라이딩한 메서드를 실행한다는 것이다.
인터페이스의 메서드는 public abstract final 이며 static 메서드가 아니다.
static 메서드는 공용으로 사용되는 메서드이지 오버라이딩해서 사용하는 메서드가 아니기에 Proxy로 만들 수 없다.
그러므로 Spring에서 getter 메서드를 사용하게 되면 프록시가 없기에 메서드 호출을 가로채지 못하고 자연스럽게
Java의 new 연산자가 실행되어 새로운 인스턴스를 생성하게 되는 것이다.
'Spring(ItelliJ & Gradle)' 카테고리의 다른 글
@ComponentScan, @Component (0) | 2023.04.19 |
---|---|
[Spring] AssertJ, Assertion (0) | 2023.04.07 |
스프링이란? (0) | 2023.04.06 |
[Spring] 싱글톤 패턴(Singleton Pattern) (0) | 2023.03.03 |
[Spring] 빈 조회 (0) | 2023.03.03 |
댓글