스프링 빈 스코프(prototype, request)

Jiwon Park·2023년 5월 9일

1. prototype

  • 스프링 컨테이너에 요청할 때마다 빈 새로 생성
  • 스프링 컨테이너는 빈 생성 - 의존관계 주입 - 초기화 까지만 관여하며, 종료 메서드 호출 x

singleton 빈과 함께 사용 시 사용할 때 마다 새로운 빈을 생성하도록 하는 방법

  • DI가 아닌 직접 필요한 의존관계를 찾는 DL 방식을 사용한다.

ObjectProvider의 getObject() 를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.
prototypeBeanProvider.getObject()을 통해서 항상 새로운 prototype 빈이 생성된다.

public class ProtoTypeTest {
    @Test
    void providerTest() {
        AnnotationConfigApplicationContext ac = new
                AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int count1 = clientBean1.logic();
        assertThat(count1).isEqualTo(1);
        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int count2 = clientBean2.logic();
        assertThat(count2).isEqualTo(1);
    }
    static class ClientBean {
        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;
        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }

    }
    @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");
        }
    }
}

2. request

  • 웹 스코프로 웹 환경에서만 동작
  • HTTP 요청 하나가 들어오고 나갈 때까지 유지되며, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다.

로그 남기기 예시

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final MyLogger myLogger;
  //private final ObjectProvider<MyLogger> myLoggerProvider;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request){
        String requestURL = request.getRequestURI().toString();
      //MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.setRequestURL(requestURL);

        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "ok";
    }
}
@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final MyLogger myLogger;
  //private final ObjectProvider<MyLogger> myLoggerProvider;
    public void logic(String id) {
      //MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.log("service id = " + id);
    }
}
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {

    private String uuid;
    private String requestURL;

    public void setRequestURL(String requestURL) {
        this.requestURL = requestURL;
    }

    public void log(String message){
        System.out.println("[" + uuid + "]" + "[" + requestURL + "] " + message);
    }

    @PostConstruct
    public void init(){
        uuid = UUID.randomUUID().toString();
        System.out.println("[" + uuid + "] request scope bean create : " + this);
    }

    @PreDestroy
    public void close(){
        System.out.println("[" + uuid + "] request scope bean close : " + this);
    }
}
  • HTTP 요청 하나가 들어와야지 정상 처리가 되기 때문에 DL방식을 사용해야 한다.
  • ObjectProvider의 getObject()를 호출하는 시점까지 스프링 컨테이너에 요청을 지연해 정상 처리 된다.
  • ObjectProvider.getObject()를 LogDemoController, LogDemoService 에서 각각 따로 호출해도 같은 HTTP 요청이면 같은 스프링 빈이 반환된다.

@Scope에 proxyMode = ScopedProxyMode.TARGET_CLASS속성을 추가하여 프록시 방식을 사용하면 코드 수를 줄일 수 있다.

  • 적용 대상이 인터페이스면 TARGET_CLASS 대신 INTERFACES를 사용한다.
  • CGLIB라는 라이브러리로 클래스를 상속 받은 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 다른 빈에 미리 주입한다.
  • 가짜 프록시 객체에는 이후에 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다. (가짜 프록시 객체는 싱글톤처럼 동작한다.)

따라서 myLogger.log()을 호출했을 때 가짜 프록시 객체의 메서드를 호출하지만 그 가짜 프록시 객체가 request 스코프의 진짜 myLogger.log()를 호출한다. 가짜 프록시 객체는 원본 클래스를 상속받아서 만들어졌기 때문에 이 객체를 사용하는 클라이언트는 원본인지 가짜인지 상관없이 동일하게 사용할 수 있다.(다형성)

profile
안녕하세요

0개의 댓글