[CS] 29) 스프링 빈 스코프

songh·2024년 3월 11일
0

CS지식

목록 보기
30/35
post-thumbnail

빈 스코프란

일반적으로 빈이란 스프링 컨테이너가 시작할때 함께 생성되고 스프링 컨테이너가 종료될 때까지 유지되는 것을 말한다. 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문이다. *스코프란, 빈이 존재할 수 있는 범위

스프링에서는 다양한 스코프를 지원한다.

1) 싱글톤 스코프

디폴트 스코프로, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프다.

2) 프로토타입 스코프

스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더 관리하지 않는 매우 짧은 범위의 스코프다. 클라이언트에게 빈을 반환한 후, 생성된 프로토타입 빈을 관리할 책임은 클라이언트에게 있다. 따라서 @PreDestory같은 종료콜백 메서드는 호출되지 않는다.

3) 웹 관련 스코프

request : 웹 요청이 들어오고 나갈때까지 유지되는 스코프
session : 웹 세션이 생성되고 종료될때까지 유지되는 스코프
application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

빈 스코프는 아래처럼 지정이 가능하다.

@Scope("prototype")
@Component
public class NetworkClient {
}

@Configuration
class LifeCycleConfig {
    @Scope("prototype")
    @Bean
    public NetworkClient networkClient() {
        final NetworkClient networkClient = new NetworkClient();
        return networkClient;
    }
}

프로토타입 스코프

싱글톤 스코프 빈을 조회하면, 스프링 컨테이너는 항상 같은 인스턴스를 반환한다. 반면 프로토타입 스코프의 경우, 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환한다. 또 생성된 이후에 스프링 컨테이너에 의해 관리되지 않는다는 점도 싱글톤 스코프와 차이점이 있고, 따라서 종료메서드는 호출되지 않는다.

아래코드처럼, 프로토타입 스코프 빈의 경우 빈을 조회할때마다 빈의 생성과 의존관계 주입, 초기화가 이뤄진다는 것을 알 수 있고, 스프링 컨테이너가 종료될때도 destroy 메서드는 호출되지 않는다는 것을 볼 수 있다.


class PrototypeTest {
    @Test
    void prototypeBeanFind() {
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
        System.out.println("find prototypeBean1");
        final PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
        System.out.println("find prototypeBean2");
        final PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
        assertThat(prototypeBean1).isNotSameAs(prototypeBean2);
        ac.close();
    }

    @Scope("prototype")
    static class PrototypeBean {
        @PostConstruct
        public void init() {
            System.out.println("SingletonBean.init");
        }

        @PreDestroy
        public void destroy() {
            System.out.println("SingletonBean.destroy");
        }
    }
}

프로토타입 스코프 빈과 싱글톤 타입 빈이 함께 사용시 문제점

프로토타입 스코프 빈은 해당 타입의 빈이 스프링 컨테이너에 요청될 때마다 생성된다. 그런데 싱글톤 타입의 빈이 프로토타입의 빈을 주입받는다면, 싱글톤 스코프 빈이 생성되고 의존관계가 주입되는 시점에만 프로토타입 빈이 조회될 것이고, 이후에는 계속 같은 빈이 사용될 것이다. 대부분의 경우, 이를 의도하지 않았을 것이다. 매번 새로운 객체가 필요하기 때문에 프로토타입으로 선언했을 것이기 때문이다. 따라서 아래처럼 테스트코드를 실행한다면, 테스트는 실패한다.

싱글톤 빈은 생성시점에서만 의존관계인 프로토타입 빈을 주입받는다. 따라서 싱글톤과 함께 계속 유지되는 것이 문제다. 따라서 프로토타입 빈을 주입시점에만 새로 생성하는게 아니라, 사용할때마다 새로 생성해 사용해야 한다.


class SingletonWithPrototypeTest1 {
    @Test
    void singletonClientUserPrototype() {
        final AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class, ClientBean.class);
        final ClientBean clientBean1 = ac.getBean(ClientBean.class);
        final int count1 = clientBean1.logic();
        assertThat(count1).isEqualTo(1);

        final ClientBean clientBean2 = ac.getBean(ClientBean.class);
        final int count2 = clientBean2.logic();
        assertThat(count2).isEqualTo(1);

        ac.close();
    }

    @Scope("singleton")
    static class ClientBean {
        private final PrototypeBean prototypeBean; // 생성 시점에 주입

        public ClientBean(final PrototypeBean prototypeBean) {
            this.prototypeBean = prototypeBean;
        }

        public int logic() {
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }
    }

    @Scope("prototype")
    static class PrototypeBean {
        private int count = 0;

        public void addCount() {
            count++;
        }

        public int getCount() {
            return count;
        }

        @PostConstruct
        public void init() {
            System.out.println("PrototypeBean.init " + this);
        }

        @PreDestroy
        public void destroy() {
            System.out.println("PrototypeBean.destroy");
        }
    }
}

프로토타입 스코프 빈과 싱글톤 스코프 빈을 함께 사용할 경우 Provider로 문제를 해결한다.

싱글톤 빈과 프로토타입 빈을 사용할때, 프로토타입 빈을 사용할때마다 항상 새로운 프로토타입 빈을 생성하려면 어떻게 해야할까?

가장 간단한 프로토타입과 싱글톤타입 빈을 함께 사용하는 방법은, 의존관계를 주입받지 않고, ApplicationContext를 통해 매번 컨텍스트 전체를 주입받게 되면, 스프링에 종속적인 코드가 되고 단위테스트도 어려워진다.


@Scope("singleton")
static class ClientBean {
    @Autowired
    private ApplicationContext ac;

    public int logic() {
        final PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
        prototypeBean.addCount();
        return prototypeBean.getCount();
    }
}

1. ObjectFactory, ObjectProvider

ObjectProvider는 지정한 빈을 컨테이너에서 대신 찾아주는 DL(Dependency Lookup) 서비스를 제공한다. 원래는 ObjectFactory에만 있었는데, 여기에 편의기능을 추가해서 ObjectProvider가 만들어졌다.

@Scope("singleton")
static class ClientBean {
    private final ObjectProvider<PrototypeBean> prototypeBeanProvider;

    public ClientBean(final ObjectProvider<PrototypeBean> prototypeBeanProvider) {
        this.prototypeBeanProvider = prototypeBeanProvider;
    }

    public int logic() {
        final PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
        prototypeBean.addCount();
        return prototypeBean.getCount();
    }
}

특징

1) 스프링이 제공하는 기능을 사용하긴 하지만, 기능이 단순해서 단위테스트를 만들거나 mock코드를 만들기 쉬워진다.
2) 딱 필요한 DL(Dependency Lookup) 기능만 제공한다.

2) JSR-330 Provider

javax.inject.Provider 라는 JSR-330 자바 표준을 사용하는 방법이다. 이 방법을 사용하기 위해서는 javax.inject:javax.inject:1 라이브러리를 gradle에 별도로 추가해야 한다.

@Scope("singleton")
static class ClientBean {
    private final Provider<PrototypeBean> prototypeBeanProvider;

    ClientBean(final Provider<PrototypeBean> prototypeBeanProvider) {
        this.prototypeBeanProvider = prototypeBeanProvider;
    }

    public int logic() {
        final PrototypeBean prototypeBean = prototypeBeanProvider.get();
        prototypeBean.addCount();
        return prototypeBean.getCount();
    }
}

특징

1) 자바 표준이므로 스프링이 아닌 다른 컨테이너에서 사용할 수 있고
2) 기능이 단순해서 단위테스트나 mock코드를 만들기 쉬워진다.
3) 필요한 기능인 DL기능만 제공한다.
4) 별도의 라이브러리가 필요하다.

웹 스코프

웹 스코프는 웹 환경에서만 동작한다. 프로토타입 스코프와는 달리, 스프링 컨테이너가 해당 스코프 빈의 생성부터 종료시점까지 관리한다. 따라서 종료 메서드가 호출된다.

웹 스코프 종류

1) request : HTTP 요청 하나가 들어오고 나갈때까지 유지되는 스코프로, 가각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다,
2) session : HTTP session과 동일한 생명주기를 가지는 스코프
3) application : 서블릿 컨텍스트와 동일한 생명주기를 갖는 스코프
4) websocket : 웹 소켓과 동일한 생명주기를 갖는 스코프

사용방법

1) ObjectProvider 사용
2) 프록시 사용

스프링 빈 등록시 웹 스코프를 그대로 주입하면 오류가 발생한다. 웹 스코프(request 스코프)는 HTTP요청이 올때 생성하고 응답하면 사라지므로 의존관계 주입이 불가능하다. 이때 프록시를 사용하면 해결할 수 있다.

proxyMode를 사용하면 마치 싱글톤 스코프 빈을 주입받는 것처럼 코드를 간결하게 만들 수 있다. 적용 대상이 클래스라면 proxyMode = ScopedProxyMode.TARGET_CLASS, 인터페이스라면 proxyMode = ScopedProxyMode.INTERFACES를 선택한다.

프록시모드를 사용하면, 스프링은 CGLIB 라이브러리를 이용해서 내 클래스를 상속받는 프록시 객체를 만들어서 주입한다. 따라서 스프링 컨테이너가 생성되고 빈의 의존관계를 주입할때 프록시 객체가 주입되는 것이다.

가짜 프록시 객체는 HTTP 요청이 들어올때 내부에서 진짜 빈을 요청하는 위임로직을 가지고 있다. 프록시 객체는 원본 클래스를 상속해서 만들어졌기 때문에, 이 객체를 사용하는 클라이언트 입장에서는 원본인지 모르게 동일하게 사용할 수 있다.

프록시를 사용할때 주의할 점

싱글톤을 사용하는 것처럼 보이지만, 이와 다르게 동작하기 때문에 주의해서 사용해야 한다. 또, 이런 특별한 scope는 꼭 필요한 곳에서만 최소화해서 사용해야지 무분별하게 사용한다면 유지보수가 어려워진다.

0개의 댓글

관련 채용 정보