반응형

@Configuration과 싱글톤


 

AppConfig를 다시보자..

package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//의존관계 주입!
@Configuration // 설정을 구성한다고 알려줌
public class AppConfig {

    @Bean//스프링 빈으로 등록
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository()); //memberRepository 호출 => 생성
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    //주문 서비스는 저장소와 할인정책이 필요하다.
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());//memberRepository호출 => 생성
    }

    @Bean
    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }

}

//과연 memberRepository => new MemoryMemberRepository();가 두번이나 되었을까??
//?? 모를땐 Test코드로 assertThat Same으로 참조값을 비교해보자...

과연 new MemoryMemberRepository();를 두번하여 각각 다른 객체 인스턴스가 만들어질까?

따라서 싱글톤이 깨질까?

결과적으로 말하면 서로 같은 객체 인스턴스이고, 호출도 한번만 되어진다.

 

??????? => 마! 이게 스프링이다....

 

 

@Configuration과 바이트코드 조작의 마법


스프링 컨테이너 = 싱글톤 레지스터

따라서 스프링 빈이 싱글톤이 되도록 보장해주어야한다.

하지만, 스프링이 자바코드까지 어떻게 하기는 어렵다.

자바코드상으론 분명 3번 호출되어야된다!!

어떻게 이걸 해결했을까???

 

바로 스프링이 클래스의 바이트코드를 조작하는 라이브러리를 사용했다.

모든 비밀은 @Configuration을 적용한 AppConfig에 있다.

 

    @Test
    void configurationDeep(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        
        //AppConfig또한 스프링 빈으로 등록이 되어진다.
        AppConfig bean = ac.getBean(AppConfig.class);

        System.out.println("bean = " + bean);
    }

결과는......

순수한 클래스일 경우 bean = hello.core.AppConfig와 같이 출력되어야횐다.

"bean = hello.core.AppConfig$$EnhancerBySpringCGLIB$$4ab6406e@7e276594"

??

바로 CGLIB라는 바이트 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고

그 클래스를 스프링 빈으로 등록했다.

 

AppConfig ->등록X

AppConfig 를 상속받은 클래스(스프링이 조작해둠) -> 등록

 

즉, 그 다른 임의의 클래스가 싱글톤을 보장해준다.

아마도..

    @Bean
    public MemberRepository memberRepository() {

        if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) {
            return 스프링 컨테이너에서 찾아서 반환;
        } else { //스프링 컨테이너에 없으면
            기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록
            return 반환
        }
    }

 

@Bean이 붙은 메서드이면 스프링 빈으로 존재하면 그 빈을 반환하고, 존재하지 않으면 그때 생성해서 반환하는 코드가 동적으로 만들어진다.

 

여기서,, 그러면 @Configuration을 적용하지 않고 @Bean만 적용하면?

=> 싱글톤이 보장 안되어짐...

따라서 memoryMemberRepository가 여러번 호출되고 각 각 다 다른 인스턴스가 만들어진다.

 

즉, 결론은 스프링을 사용하면 다 해결된다.

따라서 구성영역이면 @Configuration을 사용하자.

 

스프링 빈으로 등록되기 때문에 @Configuration이 있든 없든 스프링빈에 등록될 때는 싱글톤으로 생성이 되는 것은 맞습니다.

그런데 문제는 다음 부분입니다.

 

@Bean

MemberService memberService() {

return new MemberServiceImpl(memberRepository());

}

@Bean

public MemberRepository memberRepository() {

return new MemoryMemberRepository();

}

memberSerivce를 스프링빈으로 등록할 때 위의 memberService() 메서드가 호출됩니다. 이 메서드가 호출되면 new MemberServiceImpl()을 생성하면서 memberRepository() 메서드를 호출해서 의존관계를 찾습니다.

그런데 @Configuration이 없으니 memberRepository() 메서드가 스프링 코드인지 아닌지 인식하지 못하고, 순수한 자바 메서드로 호출 해버립니다. 그러면 내부에서 new MemoryMemberRepository()가 호출되는 것이지요. 결국 스프링의 도움을 받지 못하고 스프링 빈이 아닌 순수 자바로 생성된 new MemoryMemberRepository()가 주입되어 버립니다. 이것은 스프링이 관리하는 객체가 아니라 방금 발생한 메서드 호출로 새로 생성된 객체입니다!

결국 다음과 같이 되는 것이지요.

@Autowired MemberService memberService; //여기 내부에 들어있는 memberRepository는 스프링 빈이 아님 단순히 new로 생성한 MemoryMemberRepository임

@Autowired MemberRepository memberRepository; //스프링 빈이 등록한 memberRepository

memberService.getMemberRepository() != memberRepository //따라서 둘은 다른 객체

 

 

728x90
반응형
블로그 이미지

아상관없어

,