빈 Scope?

maketheworldwise·2022년 4월 10일
0


이 글의 목적?

IoC 컨테이너에 등록된 모든 빈들은 Scope이라는 개념이 있다. 정리해보자. 😆

Singleton

기본적으로 등록되는 빈들의 Scope는 싱글톤이다. 싱글톤은 애플리케이션의 전반에 걸쳐 해당 빈의 인스턴스가 오직 하나만을 가진다는 것을 의미한다.

실제로 싱글톤으로 등록된 빈들을 조회해보면 동일한 인스턴스를 가지고 있다는 것을 확인할 수 있다.

@Component
public class Proto {
}
@Component
public class Single {

    @Autowired
    Proto proto;

    public Proto getProto() {
        return proto;
    }
}
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    Single single;

    @Autowired
    Proto proto;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 싱글톤 스코프는 같은 인스턴스를 사용
        System.out.println(proto); // AppRunner 클래스에서 주입 받아온 proto
        System.out.println(single.getProto()); // Single이 참고하고 있는 proto
    }
}

Prototype

싱글톤 Scope 외에도 다른 옵션들이 존재한다.

  • Prototype
    • Request
    • Session
    • WebSocket
    • ThreadScope

나열된 모든 옵션들은 모두 프로토타입과 비슷하다. 프로토타입 Scope만 본다면, 싱글톤과 다르게 매번 빈을 받아올 때마다 새로운 인스턴스를 생성한다.

@Component
@Scope("prototype")
public class Proto {
}
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
	ApplicationContext ctx;

    @Override
    public void run(ApplicationArguments args) throws Exception {
    	// 매번 다른 인스턴스를 사용
		System.out.println(ctx.getBean(Proto.class));
		System.out.println(ctx.getBean(Proto.class));
		System.out.println(ctx.getBean(Proto.class));
    }
}

섞어서 사용할 경우

싱글톤과 같이 특정 빈에서 짧은 생명 주기를 가진 빈들을 주입받을 때에는 다음과 같은 경우를 생각해야한다고 한다.

프로토타입 빈이 싱글톤 빈을 참조할 경우

이 경우에는 아무런 문제가 발생하지 않는다. 프로토타입 빈은 매번 인스턴스가 새로 생성이 되지만, 프로토타입 빈이 참조하는 싱글톤 빈은 항상 동일하기 때문에 의도한 바에서 벗어나지 않아 큰 문제가 없다.

@Component
@Scope("prototype")
public class Proto {
	@Autowired
    Single single;
}

싱글톤 빈이 프로토타입 빈을 참조할 경우

이 경우는 생각해볼 필요가 있다.

싱글톤 빈은 단 하나의 인스턴스만 생성이 된다. 그리고 그 내부에 참조한 프로토타입 빈도 설정이 같이 된다. 그렇기 때문에 싱글톤 빈을 사용하더라도 내부에 참조한 프로토타입의 빈은 변경되지 않는다. 즉, 매번 새로운 인스턴스를 만드려는 의도와 다르게 단 하나만의 변경되지 않는 빈을 가지게 되어 문제가 발생한다는 의미다.

@Component
public class Single {

    @Autowired
    Proto proto;

    public Proto getProto() {
        return proto;
    }
}
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
	ApplicationContext ctx;

    @Override
    public void run(ApplicationArguments args) throws Exception {
    	// 동일한 싱글톤 인스턴스와 그 내부에 참조하고 있는 동일한 프로토타입 인스턴스
		System.out.println(ctx.getBean(Single.class).getProto());
		System.out.println(ctx.getBean(Single.class).getProto());
		System.out.println(ctx.getBean(Single.class).getProto());
    }
}

그럼 어떻게 해결할 수 있을까?

프록시로 해결하기

싱글톤 빈이 프로토타입 빈을 직접 참조하지 않도록 프록시로 빈을 감싸주어 해결할 수 있다.

@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) // CGLIB를 이용한 다이나믹 프록시
public class Proto {
	@Autowired
    Single single;
}

이미지를 보면 알 수 있듯이, 포로토타입을 감싼 프록시 인스턴스를 빈으로 등록하고, 이 빈을 싱글톤 빈이 참조하고 있는 빈에게 넣어주는 것이다. (여기서 프록시 빈은 내부의 빈을 상속받아 구현했기 때문에 타입은 동일하여 주입이 가능하다.)

💡 TARGET_CLASS? CGLIB란?

원래 자바에서의 다이나믹 프록시는 인터페이스 프록시만 제공하지만 CGLIB는 서드 파티 라이브러리로, 클래스도 프록시로 만들어준다. 따라서 위의 경우에는 TARGET_CLASS 설정으로 CGLIB 기반의 클래스 프록시를 만들어주는 것이다. 만약 인터페이스였다면, INTERFACE 옵션을 사용할 수 있다.

주입받는 빈의 타입을 변경

ObjectProvider에는 스프링 코드가 들어가서 백기선님은 별로 좋아하지 않으신 듯하다.

@Component
public class Single {

    @Autowired
    ObjectProvider<Proto> proto;

    public Proto getProto() {
        return proto.getIfAvailable();
    }
}

거의 대부분 싱글톤 Scope을 사용한다고 하니, 지금의 내 수준에서는 많이 생각할 필요는 없어보이지만 Scope에 대한 설정만으로도 다양한 작업을 처리할 수 있을 것 같다는 생각이 든다.

이 글의 레퍼런스

  • 백기선님의 스프링 프레임워크 핵심 기술
profile
세상을 현명하게 이끌어갈 나의 성장 일기 📓

0개의 댓글