framework/spring

스프링 핵심 원리 이해 8 - bean scope

wooweee 2023. 10. 29. 22:49
728x90

1. bean scope

  • scope: 범위, bean이 존재할 수 있는 범위

 

1.1. scope 종류

  1. 싱글톤
    • 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
  2. 프로토타입
    • 스프링 컨테이너가
      1. 프로토타입 빈의 생성 → 의존관계 주입 → 초기화 메서드 까지 관여 (spring bean 생명 주기)
      2. 이후는 관리하지 않는 매우 짧은 범위의 스코프
    • 종료 callback 없음
  3.    웹 관련 스코프
    • request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
    • session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
    • application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

 

  • 싱글톤, 프로토타입, request 정도 알면 충분
  • 웬만하면 싱글톤으로 처리를 하는 것을 권장
    scope default 값 : singleton 
  • 프로토타입, request scope 무분별 사용시 , 유지 보수 어려움

 

1.2. scope 등록 방식

// 자동 등록
@Component
@Scope("prototype")
public class HelloBean{}

// 수동 등록
@Bean
@Scope("prototype")
public class HelloBean{}

 

2. 프로토타입 스코프

  • summary
    • prototype 빈 : client 요청과 관계 없이 호출만 하면 생성된다.
    • request 빈 : client 요청이 올 때 bean이 생성된다.

    • prototype은 호출을 임의로 늦추고 싶은 것이고 request bean은 규칙상 늦게 생성되는 빈을 오류 없이 인정 받고 싶다.
      -> provider가 도움을 준다.

 

2.1. 싱글톤, 프로토타입 차이점

  • 싱글톤 타입: 요청시, 이미 생성된 공용 객체 사용
  • 프로토 타입: 요청시, 요청마다 객체 생성 - 항상 새로운 프로토타입 빈 생성

 

  • 스프링 컨테이너
    • 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리하고 관리 안한다.
    • @preDestroy 종료 메서드 호출 안된다.
    • 종료가 필요한면 client 측에서 직접 종료를 호출해야한다.

위: singleton 아래: prototype

  • singleton
package hello.core.scope;

@Test
public void singletonBeanFind(){
	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);
	// ac에서 spring bean 등록하면서부터 이미 객체 생성됨
    
    SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
    SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
    System.out.println("singletonBean1 = " + singletonBean1); // 동일 객체 주소
    System.out.println("singletonBean2 = " + singletonBean2); // 동일 객체 주소
    assertThat(singletonBean1).isSameAs(singletonBean2);
    
    ac.close(); // @preDestroy 동작
}

@Scope("singleton")
static class SingletonBean{
    @PostConstruct
    public void init(){ System.out.println("SingletonBean.init"); }
    @PreDestroy
    public void destroy(){ System.out.println("SingletonBean.destroy"); }
}
  • prototype
    • 프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리해야 한다. 
    • 종료 메서드에 대한 호출도 클라이언트가 직접 해야한다.
    • 요청할때마다 계속 생성을 하고 싶다 = prototype을 사용하면 된다.
package hello.core.scope;

@Test
public void PrototypeBeanFind(){
	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
    // 빈을 조회해야지 작동
    PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class); 
    PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
    System.out.println("prototypeBean1 = " + prototypeBean1); // 다른 객체 주소
    System.out.println("prototypeBean2 = " + prototypeBean2); // 다른 객체 주소
    assertThat(prototypeBean1).isSameAs(prototypeBean2);
    
    // 굳이 빈 제거 원하면 직접 method 호출
    prototypeBean1.destroy();
    prototypeBean2.destroy();
    
    ac.close(); // @PreDestroy 작동 안함
}

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

 

2.2. 싱글톤 with 프로토타입

  • prototype 사용 목적 : 사용할 때마다 새로 생성해서 사용하는 것을 원한다.

  • 문제
    • 프로토타입만 사용하는 bean만 존재할 시 문제가 없다.

    • 싱글톤 bean 안에 의존관계 주입으로 prototype bean이 들어가면 문제가 발생
    • 싱글 톤에서 prototype bean 생성 후 사용 시, prototype bean의 참조값을 들고 있기 때문에 prototype bean이 싱글톤 scope와 동일하게 유지
    • 물론 여러 종류의 singleton bean이 동일 prototype bean을 주입 받을 시, prototype bean은 새로 생성

 

  • 예제 : 클라이언트 B도 count가 1이 되어야 하는데,  싱글톤 bean은 의존관계 주입까지 다 받은 상태(이 때 이미 prototype bean이 생성)에서는 다시는 객체를 생성하지 않으므로 prototype bean이 고정이 되어버리게 된다.
@Test
    void singletonClientUsePrototype() {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);

        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int logic = clientBean1.logic();
        assertThat(logic).isEqualTo(1);

        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int logic1 = clientBean2.logic();
        assertThat(logic1).isEqualTo(2); // 1이 아니라 2가 된다.

    }

    @Scope("singleton")
    static class ClientBean {

        private final PrototypeBean prototypeBean; // PrototypeBean DI 주입
        
        @Autowired
        public ClientBean(PrototypeBean prototypeBean) {
            this.prototypeBean = prototypeBean;
        }
        
        // singleTon에 종속된 prototype 사용 method
        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"); }
    }
}

 

  • 해결
    • DL을 통해서 해결 
      • 의존관계를 외부에서 주입(DI) 받는게 아니라 의존 관계 주입을 잠시 미뤄서 필요할 때, 필요한 의존관계를 찾는 것을 Dependency Lookup (DL) 의존관계 조회(탐색)이라고 한다. 
    • spring 의존 DL 종류
      1. ObjectFactory: 과거에 사용, getObject();  - 안씀
      2. ObjectProvider: 요즘 사용, getObject() + 부가 기능 존재

 

3. Provider로 문제 해결 _ prototype bean 주입 늦추기

  • DL 3가지 방법
    1. 안좋은 방법 (내가 직접 springApplicationContext 관리)
    2. spring provider (Object provider,)
    3. java provider
  • 정리
    • 웬만하면 ObjectProvider 사용
    • 만약(가능성 0.1%)에 다른 컨테이너에서 DL이 필요시 Provider 사용

 

3.1. 스프링 컨테이너 전체 주입 받은 후 DL 수행 

  • 스프링 컨테이너에 종속적인 코드 -> 단위 테스트 어려움
  • 필요 이상의 data 받음
@Scope("singleton")
static class ClientBean {

    @Autowired
    private ApplicationContext ac;

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

 

3.2. ObjectProvider  DL

  • spring에서 제공하는 bean이다. = spring에 의존적
  • 동작 원리
    • 그동안 했던 방식은 실제 사용할 bean을 DI 했었다.
    • 하지만 해당 bean 사용 목적은 필요할 때만 호출해서 사용하기 위함 == DI. 직접 DI 되면 안된다.
    • 그래서 Provider를 대신 DI한다.
    • Provider는 실제로 DI 해야될 bean이 호출 되는 code에서 getObject()를 통해서 해당 빈을 호출해주기 때문
    • 결론적으로 provider가 대신 di에 위치하면서 실제 di 할 bean이 호출되는 code에 getObject()통해서 실제 bean을 호출해준다.
      == DI를 늦춰준다.
  • 참고
    • ObjectProvider도 bean으로 등록
    • DI로 실제 주입되어야하는 Bean 대신에 임시로 Provider 빈이 주입 되고 실제 사용시 Provider가 진짜 DI 해야하는 bean을 찾아서 바꿔치기 해준다.
package hello.core.scope;

public class SingletonWithPrototype2 {
    @Test
    @DisplayName("singleton-prototype 연결 시, ObjectProvider 사용")
    void singletonClientUsePrototype() {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int logic = clientBean1.logic();
        assertThat(logic).isEqualTo(1);
        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int logic1 = clientBean2.logic();
        assertThat(logic1).isEqualTo(1); // prototype이 제기능을 함. DL 작동 함.

    }
    @Scope("singleton")
    static class ClientBean{
        /* singleton-prototype 연결 시, ObjectProvider로 해결 */

        // private ObjectFactory<PrototypeBean> prototypeBeanProvider; // 옛날 version
        private ObjectProvider<PrototypeBean> prototypeBeanProvider; // 요즘 version

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

        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject(); // DL : 이 때 prototypeBean을 찾아서 반환.
            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"); }
    }
}

 

3.3. java 표준 종류 : JSR-330 Provider

  • 라이브러리 설치 필요
# gradle

dependencies {
# 스프링 부트 3.0 이상
jakarta.inject:jakarta.inject-api:2.0.1
# 스프링 부트 3.0 미만
javax.inject:javax.inject:1
}

 

  • 작동 원리 동일
    • ObjectProvider -> Provider
    • getObject -> get();
  • 장점
    • 자바 표준이고, 기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워진다. 
    • 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.
    • 순환참조 문제 해결, 지연 추가 시 사용
  • 단점
    • library 설치 필요
    • get() 기능 하나만 존재
  • 참고
    • Provider도 bean으로 등록
    • DI로 실제 주입되어야하는 Bean 대신에 임시로 Provider 빈이 주입 되고 실제 사용시 Provider가 진짜 DI 해야하는 bean을 찾아서 바꿔치기 해준다.
  • 코드
package hello.core.scope;

public class SingletonWithPrototype3 {
    @Test
    @DisplayName("singleton-prototype 연결 시, ObjectProvider 사용")
    void singletonClientUsePrototype() {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int logic = clientBean1.logic();
        assertThat(logic).isEqualTo(1);
        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int logic1 = clientBean2.logic();
        assertThat(logic1).isEqualTo(1); // prototype이 제기능을 함. DL 작동 함.

    }

    @Scope("singleton")
    static class ClientBean{
        /* singleton-prototype 연결 시, Provider로 해결 */
        private Provider<PrototypeBean> prototypeBeanProvider; // 요즘 version

        @Autowired
        public ClientBean(Provider<PrototypeBean> prototypeBeanProvider) {
            this.prototypeBeanProvider = prototypeBeanProvider;
        }

        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.get(); // DL : 이 때 prototypeBean을 찾아서 반환.
            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");
        }
    }
}

 

4. 웹 스코프

  • 웹 스코프 특징
    • 웹 환경에서만 동작
    • 프로토타입과 다르게 스프링이 해당 스코프의 종료시점까지 관리
    • 종료 메서드가 호출
    • 웹 관련 스코프는 범위만 다르지 동작 원리는 동일

 

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

 

4.1. Web library 설정

 

  • web 관련 library 등록
# build.gradle

dependencies {
	// web library
	implementation 'org.springframework.boot:spring-boot-starter-web'
}
  • 웹 환경 추가 : spring boot 환경 설치해서 내장 톰켓 서버 이용
  • spring server 수행시 해당 log 나타남
2023-10-29T21:49:45.518+09:00  INFO 45731 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''

 

  •  참고
    1. 웹 라이브러리 없을 경우 springContainer 구현체: AnnotationConfigApplicationContext
    2. 웹 라이브러리 있을 경우 springContainer 구현체: AnnotationConfigServletWebServerApplicationContext - 웹 관련 기능 필요하기 때문
  • port 중복 문제 해결
    1. application.properties : server.port=9090
    2. terminal
      1. ps-ef | grep tomcat   - 좌상단 2번째 숫자
      2. kill -9 [tomcat 번호]

 

4.2. 직접 만든 Logger - request 요청 확인용

  • My Logger: controller로부터 받은 request 값으로부터 log를 출력하는 source code
  • scope를 request으로 했으므로 request 요청이 올 때부터 My Logger 객체를 생성 가능 - 수정자 주입 사용
package hello.core.common;

@Component
@Scope(value = "request") // controller 요청이 와야지만 bean을 생성할꺼임
public class MyLogger {
	
    private String uuid;
    private String requestURL;
    
    // requestURL은 MyLogger 빈의 생성 시점을 알수 없어서 setter로 받아야한다.
    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)
    }
}

 

4.3. Controller, MyLogger 문제점

  • LogDemoController
    • controller가 springboot 가 run 될때 먼저 bean으로 등록 된다.
    • 하지만 MyLogger는 scope가 request여서 요청이 없으면 bean으로 등록되지 않는다.

    • request가 오면, MyLogger가 bean으로 등록되고 setter를 통해서 Mylogger는 의존관계 주입도 완료한다.
    • 그리고 controller의 method를 실행한다.(이때 mylogger bean을 이용해서 log를 기록 할 수 있다.)
  • 문제점
    • springboot가 run 할 때, controller가 bean으로 등록, 의존관계 주입 실행
    • 의존관계 주입시 존재해야하는 Mylogger이 존재하지 않는다. - Mylogger은 request가 있어야 생성되는 scope 범위에 존재하기 때문
  • 해결 방안
    • DL을 사용
    • 우선은 provider로 DI 임시방편으로 이용
    • client 요청이 오면 myLogger bean을 생성 -> bean에 등록 후, Provider가 Springboot container에서 해당 bean을 찾고 Controller에서 주입
    • request 종료되면 해당 빈은 제거
package hello.core.web;

@RestController
@RequiredArgsConstructor
public class LogDemoController {
    private final LogDemoService logDemoService;
    private final MyLogger myLogger;

    @RequestMapping("log-demo")
    public String logDemo(HttpServletRequest request) {
        String requestURL = request.getRequestURL().toString();
        myLogger.setRequestURL(requestURL); // MyLogger 의존관계 주입

        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "ok";
    }
}

 

4.5. 해결  방법

    4.5.1. ObjectProvider

package hello.core.web;

@RestController
@RequiredArgsConstructor
public class LogDemoController {
    private final LogDemoService logDemoService;
    private final ObjectProvider<MyLogger> myLoggerProvider;

    @RequestMapping("log-demo")
    public String logDemo(HttpServletRequest request) {
        String requestURL = request.getRequestURL().toString();
        MyLogger myLogger = myLoggerProvider.getObject(); // DL 주입

        System.out.println("myLogger = " + myLogger.getClass());
        // myLogger = class hello.core.common.MyLogger

        myLogger.setRequestURL(requestURL); // MyLogger 의존관계 주입

        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "ok";
    }
}
package hello.core.web;

@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final Provider<MyLogger> myLoggerProvider;

    public void logic(String id) {
        MyLogger myLogger = myLoggerProvider.get();
        myLogger.log("service id = " + id);
    }
}
# mylogger scope는 request기 때문에 controller-service는 동일한 log를 가지게 된다.

[23ead830-8ffa-475f-884e-74ce15416d2c] request scope bean create:hello.core.common.MyLogger@20d4145c
[23ead830-8ffa-475f-884e-74ce15416d2c][http://localhost:8080/log-demo] controller test
[23ead830-8ffa-475f-884e-74ce15416d2c][http://localhost:8080/log-demo] service id = testId
[23ead830-8ffa-475f-884e-74ce15416d2c] request scope bean close:hello.core.common.MyLogger@20d4145c

 

summary

  • prototype 빈 : client 요청과 관계 없이 호출만 하면 생성된다.
  • request 빈 : client 요청이 올 때 bean이 생성된다.

  • prototype은 호출을 임의로 늦추고 싶은 것이고 request bean은 규칙상 늦게 생성되는 빈을 오류 없이 인정 받고 싶다.
    -> provider가 도움을 준다.

  • 작동원리
    • provider 라는 bean을 대신 주입한다. provider도 빈으로 등록된다. spring에서 제공하는 bean
    • 그리고 실제 DI하려는 Bean이 생성되거나 사용되는 시점에 Provider method를 이용해서 해당 Bean을 찾아서 준다.
  • 참고
    • Provider도 bean으로 등록
    • DI로 실제 주입되어야하는 Bean 대신에 임시로 Provider 빈이 주입 되고 실제 사용시 Provider가 진짜 DI 해야하는 bean을 찾아서 바꿔치기 해준다.

 

    4.5.2. scope와 proxy

1. 사용법

  • provider 쓰는 것도 귀찮아져서 새로운 걸로 발전함

  • proxyMode = ScopedProxyMode.XXXX
    1. 적용 대상이  클래스면  TARGET_CLASS 를 선택 - public class MyLogger{}
    2. 적용 대상이 인터페이스면  INTERFACES 를 선택 - public Interface MyLogger{}
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) //target class
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_INTERFACE) //target interface
  • 원리: MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시에 미리 주입해 둘 수 있다.

 

2. 코드

package hello.core.common;

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)

public class MyLoggerProxy {
    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(); // 유니크 id 부여
        System.out.println("[" + uuid + "] request scope bean create:" + this);
    }

    @PreDestroy
    public void destroy() {
        System.out.println("[" + uuid + "] request scope bean close:" + this);

    }
}
package hello.core.web;

@RestController
@RequiredArgsConstructor
public class ProxyLogDemoController {
    private final ProxyLogDemoService logDemoService;
    private final MyLoggerProxy myLogger;

    @RequestMapping("proxy-log-demo")
    public String logDemo(HttpServletRequest request) throws InterruptedException {
        String requestURL = request.getRequestURL().toString();

        System.out.println("myLogger = " + myLogger.getClass());
        // myLogger = class hello.core.common.MyLoggerProxy$$SpringCGLIB$$0

        myLogger.setRequestURL(requestURL); // MyLogger 의존관계 주입

        myLogger.log("controller test");
        Thread.sleep(1000); // log 섞이게 하려고 넣음
        logDemoService.logic("testId");
        return "ok";
    }
}
package hello.core.web;

@Service
@RequiredArgsConstructor
public class ProxyLogDemoService {
    private final MyLoggerProxy myLogger;

    public void logic(String id) {
        myLogger.log("service id = " + id);
    }
}

 

3. 동작원리

  • 웹 스코프와 프록시 동작 원리
    • 스프링 컨테이너는 CGLIB 바이트코드를 조작하는 라이브러리를 사용해서, MyLogger를 상속받은 가짜 프록시 객체를 생성한다.
      log : class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$b68b726d
    • CGLIB로 내 클래스(=MyLogger)를 상속 받은 가짜 프록시 객체(MyLoggerCGLIB$$)를 만들어서 의존관계 주입(=DI)한다. 

 

 

  • 가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.
  • 가짜 프록시 객체는 내부에 진짜 myLogger를 찾는 방법을 알고 있다. 
  • 가짜 프록시 객체는 request 스코프(MyLogger object)의 진짜  myLogger.logic() 를 호출한다. 

 

 

4. 정리

  • 가짜 프록시 객체는 실제 request scope와는 관계가 없다. 그냥 가짜이고, 내부에 단순한 위임 로직만 있고, 싱글톤 처럼 동작한다.
    그래서 scope같은걸 고려할 필요가 없다.
  • Provider를 사용하든, 프록시를 사용하든 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다.

 

  • 주의점  
    • Provider 덕분에 prototype, requestType의 Bean이 마치 싱글톤을 사용하는 것 같지만 각각의 scope에 맞춰서 동작하기 때문에 결국 주의해서 사용해야 한다.
    • 메서드를 요청시 각각의 object가 생성된다.
    • 이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용하자, 무분별하게 사용하면 유지보수하기 어려워진다.
    • test가 까다롭다.

 

 

 

이전 발행글 : 스프링 핵심 원리 이해 7 - bean 생명주기 callback

 


출처: 인프런 스프링 핵심 원리 - 기본편