[Spring] 수동 빈 등록시 사용하는 @Configuration의 특별한 기능

위 코드를 보면 new 키워드로 객체를 반환하고 있다.

그렇다면 orderService()를 2번 호출하면 OrderService와 OrderRepository가 2번씩 생성되지 않을까?

 

결론부터 말하면 아니다.

orderService()와 orderRepository()가 호출될 때 위 처럼 메시지가 출력되도록 했다. 어떻게 나올까?

2번씩 호출되는 것이 아니라 1번 씩 호출된다. 즉 싱글톤이 보장되었다.

 

@Configuration 어노테이션

@Configuration 클래스 내에 정의된 메소드들 중 @Bean 어노테이션이 붙은 메소드들은 스프링 컨테이너에 의해 빈(bean)으로 등록되고

이러한 빈들은 스프링 컨테이너에 의해 관리된다.

또한 @Configuration 어노테이션이 붙은 클래스는 스프링 컨테이너에 의해 특별한 방식으로 처리다.

이 특별한 방식은 xxx@CGLIB라는 객체는 스프링이 xxx 클래스를 상속받는 프록시 객체를 만들고, 이 프록시 객체를 Bean으로 등록한 것이다.

그리고 이 프록시 객체에는 이미 스프링 컨테이너에 Bean이 존재하면 기존 Bean을 반환하고, 없으면 스프링 컨테이너에 Bean을 등록하는 코드가 동적으로 만들어진다.

 

@Configuration 어노테이션을 사용할 때와 사용하지 않을 때 비교

꼭 수동 bean 등록시에 해당 클래스에 @Configuration 어노테이션을 붙여야할까?

아래 코드를 통해 @Configuration 어노테이션를 사용하지 않을 때와 사용할 때를 비교해보자.

@Test
void useSingleton() {
    // given
    ApplicationContext ac = new AnnotationConfigApplicationContext(OrderConfig.class);

    OrderService orderService1 = ac.getBean("orderService", OrderService.class);
    OrderService orderService2 = ac.getBean("orderService", OrderService.class);

    OrderConfig bean = ac.getBean(OrderConfig.class);
    System.out.println("저장된 Bean 이름 : " + bean.getClass());

    // when && then
    assertThat(orderService1).isSameAs(orderService2);
}

 

@Configuration 어노테이션 사용X

@Configuration 어노테이션 없음

@Configuration을 사용하지 않았을 경우 orderService와 orderRepository가 2번씩 호출될 것을 생각했는데 

orderService는 한 번만 호출되었다. 왜 1번만 호출된걸까?

위 코드에서 OrderService는 스프링 컨테이너를 통해 직접 요청하고 있어 싱글톤이 보장된 것이다.

하지만 orderService()메서드 내부에서 orderRepository() 메서드를 호출하면 new 키워드로 OrderRepository의 새 인스턴스가 매번 생성기 때문에

orderRepository는 2번 호출된 것이다.

 

즉, 정리하자면

빈을 정의하는 클래스에 @Configuration 어노테이션이 없더라도 스프링 컨테이너에 직접 요청한 객체는 싱글톤이 보장된다.

하지만 설정 클래스 내부에서 빈 생성 메서드를 직접 호출할 경우 싱글톤이 보장되지 않는다.

설정 클래스를 사용할 때는 @Configuration 어노테이션을 붙여서 스프링이 클래스를 특별하게 처리하도록 하는 것이 좋다.

 

@Configuration 어노테이션 사용 O

@Configuration 어노테이션 있음

OrderConfig 뒤에 CGLIB가 붙은 프록시 객체가 등록된 것을 볼 수 있다.

그리고 orderService() 내부에서 호출된 orderRepository도 2번 호출되는 것이 아닌 CGLIB 프록시 객체를 통해 한 번만 호출되었다.