나만보는페이지

[Spring] Bean Scope 만들기 본문

spring

[Spring] Bean Scope 만들기

pplenty 2020. 4. 21. 01:06

[Spring] 사용자 정의 Bean Scope 만들기

싱글톤, 프로토타입 등 스프링에서 기본적으로 제공 해주는 스코프들이 있다.
기본적으로 제공되는 것 외에 내가 원하는 Scope 을 만들어서 활용해보자!
그냥 궁금하니깐 사용자 정의 빈 스코프를 만들어보자.

Bean Scope⁉️

스프링에서 빈 스코프는 쉽게 말하면 bean 의 생명 주기(수명?)를 결정하는 범위이다.
스프링이 Scope 를 이용하여 어떤 식으로 빈의 생명 주기를 관리 하는지는 기회가 되면 자세하게 써볼 예정이다.

Bean Scope 종류

스프링에서 기본적으로 제공하는 대표적인 스코프는 2가지 이다.
singleton 은 한번만 생성해서 컨테이너 종료까지 유지된다. prototype 은 빈 팩토리에 빈을 요청할 때 마다 새로 생성 해준다.
이 외에도 스프링 서브 프로젝트에 특성에 맞게 각각 정의하여 사용하고 있는 스코프들도 있다.
예를 들면, 스프링 웹에서는 request, session 등 웹의 특성에 맞는 빈 스코프가 정의되어 있다.
스프링 배치에서는 job, step 이라는 Batch Job/Step 의 생명 주기와 동일한 주기를 갖는 빈 스코프를 정의하여 사용한다.

Scope 정의

오늘 정의해서 사용해 볼 Scope 는 빈의 수명으로 따졌을 때, 프로토타입(가장 수명이 짧은)과 싱글톤(가장 수명이 긴)의 중간쯤(?) 되는 간단한 스코프를 만들 예정이다.

아래와 같이 빈의 생명 주기를 정의 한다.

빈을 처음 요청하면 새로 생성하고, 2번째 요청하면 처음에 생성한 빈을 반환하고 빈의 생명 주기는 끝난다.
3번째 요청에서는 다시 빈을 생성하고, 4번째 요청에는 3번째 요청에 생성된 빈을 반환 한다.

최대한 간단하게 만들어 보려고 위와 같이 정의했는데, 네이밍을 뭐라고 해야할지 모르겠다.
음… 두번의 빈 요청 동안만 유지 되므로 twice 라고 해야겠다.

Scope 구현

Scope(어노테이션이 아닌 org.springframework.beans.factory.config 패키지에 속한 인터페이스 이다) 을 implements 하여 아래와 get 메서드를 오버라이드 한다. 단일 원칙 책임상 빈을 저장해 놓을 저장소를 별도 클래스로 분리하는 것이 맞지만, 여기서는 간단하게 정적 변수인 Map을 이용해 빈이 한번만 캐싱 되도록 구현한다.
빈 팩토리의 getBean 을 이용하여 생명주기만 확인해볼 것이므로 나머지 메서드 구현은 공백으로 두거나, null 을 반환 하도록 한다.

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class TwiceScope implements Scope {

    private static final Map<String, Object> CACHE = new ConcurrentHashMap<>();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Object scopedObject = CACHE.get(name);
        if (scopedObject == null) {
            scopedObject = objectFactory.getObject();
            CACHE.put(name, scopedObject);
        } else {
            CACHE.remove(name);
        }
        return scopedObject;
    }
}

Twice Scope 등록

스코프를 만들었으니, 빈 팩토리에 등록해줘야 한다. 빈 팩토리 후 처리기(BeanFactoryPostProcessor) 를 이용하여 스코프를 등록해 주자.
빈 팩토리 후 처리기의 실행은 ApplicationContext refresh 과정(5) 중에 일어난다.

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyScopeRegisterBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        beanFactory.registerScope("twice", new TwiceScope());
    }
}

테스트 용 빈 설정

이너 클래스 A를 선언하고, 해당 타입의 twice 스코프를 가지는 빈을 @Bean 을 이용하여 선언 한다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class TestBeanConfig {

    @Bean
    @Scope("twice")
    public A onlyTwice() {
        // A 타입의 onlyTwice 빈 이름을 갖는 팩토리 메서드
        return new A();
    }

    static class A {
    }
}

테스트 코드

간단한 테스트 코드를 통해 Scope 가 적용 되는지 확인 해본다.

@SpringBootTest(classes = {CustomBeanScopeApplication.class, TestBeanConfig.class}) // 빈 설정과 후 처리기 스캔을 위한 클래스 설정
class TwiceScopeTest {

    @Autowired
    ApplicationContext applicationContext;

    @DisplayName("2번만 유지 되는 빈 스코프 확인")
    @Test
    void get() {

        // given
        String beanName = "onlyTwice";

        // when
        Object scopedBean1 = applicationContext.getBean(beanName);
        Object scopedBean2 = applicationContext.getBean(beanName);
        Object scopedBean3 = applicationContext.getBean(beanName);
        Object scopedBean4 = applicationContext.getBean(beanName);

        // then
        System.out.println("1st getBean : " + scopedBean1);
        System.out.println("2nd getBean : " + scopedBean2);
        System.out.println("3rd getBean : " + scopedBean3);
        System.out.println("4th getBean : " + scopedBean4);

        assertThat(scopedBean1).isEqualTo(scopedBean2); // 1, 2번째는 같은 빈 반환
        assertThat(scopedBean2).isNotEqualTo(scopedBean3); // 3번째는 새로운 빈을 생성하기 때문에 2번째와 다른 빈 반환
        assertThat(scopedBean3).isEqualTo(scopedBean4); // 3, 4번째는 같은 빈 반환
    }
}

테스트 결과

테스트는 모두 통과 한다. 로그를 찍어 보면 1-2번째가 같은 객체이고, 3-4번째 빈이 같은 객체 라는걸 확인해 볼 수 있다.
스코프 테스트 결과

Comments