지금까지 학습했던 내용으로는 2가지 정도가 있다.
앞으로도 학습함에 따라 계속해서 추가해 나갈 예정이다.
1. static으로 선언된 Bean 등록 메서드
Spring Container에 등록된 Bean에 대한 인스턴스가 실제로 Singleton 패턴이 적용되어 있는지 확인해 보려고 테스트 코드를 작성하였다.
근데, 이상하게도 객체 참조 값이 모두 달라 검증이 되지 않았다.
알고보니 테스트하고 있던 AppConfig에서 해당 Bean을 등록할 때 static으로 선언해서 발생한 문제였다.
...
@Configuration
public class AppConfig {
...
@Bean
public static MemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
...
}
근데 왜 static 하게 Bean 등록 메서드를 사용하면 객체가 Singleton 패턴을 보장받지 못하는걸까?
이유는 다음과 같다.
Spring은 내부적으로 @Bean이 붙은 메서드를 호출하면 Singleton Container에 동일한 Bean이 존재하는지 Proxy를 통해 확인한다.
이때, static Method의 경우 Proxy가 적용되지 않아 Singleton Container에 동일한 Bean이 없다고 판단하고 계속 새로운 객체를 반환하게 되는 것이다.
조금만 더 설명하면 다음과 같다.
static 으로 메서드를 선언하게 되면 Class Loader에 의해 해당 클래스를 메모리에 로드할 때 메모리에 함께 배치된다. 이는, 특정 인스턴스에 속하지 않은 상태를 말한다.
그러나 Spring Container의 경우 인스턴스 레벨에서 라이프사이클 관리, 의존성 주입, 프록시 적용 등을 수행하므로 @Bean 을 통해 Spring Container에 Bean으로 등록해 사용하고 싶다면 non-static Method 로 선언해야 하는 것이다.
2. @Configuration 를 사용하지 않는 경우
Spring Container는 자바 코드를 보다 제어하기 위해서 바이트 코드를 조작하는 CGLIB 라이브러리를 사용한다.
@Configuration 를 통해 Spring에서 내부적으로 CGLIB 기술을 사용해 Singleton 패턴을 보장하는 것이다.
...
@Configuration
public class AppConfig {
...
}
만약 AppConfig가 @Configuration 이 있는 상황이라면 객체 인스턴스 값은 다음과 같이 출력된다.
bean = class hello.core.AppConfig$$SpringCGLIB$$XXXXXX
없다면 AppConfig가 @Configuration 객체 인스턴스 값은 다음과 같이 출력된다.
bean = class hello.core.AppConfig
즉, AppConfig를 상속 받고 임의의 클래스를 만들어 CGLIB 기술을 사용해 Spring 이 내부적으로 자바를 제어하려는 노력을 하고 있다는 것을 알 수 있다.
따라서, @Bean 만 사용하더라도 스프링 빈으로 등록할 수 있으나, Singleton 패턴을 보장하지는 않는다.
최초에 @Bean이 등록된 메서드에 의해 호출된 객체는 Spring Container에 등록되지만, 의존 관계에 의해서 호출된 다른 메서드들은 Singleton 패턴을 적용받지 못하기 때문이다.
여기서 의존 관계에 의해서 호출된다는 의미는 외부로부터 여러 번 실행되는 생성자 함수를 말한다.
따라서, 스프링 설정 정보에는 항상 @Configuration 을 사용해야 한다.