본문 바로가기
Study/Java Spring Boot

[Spring] 핵심 원리 9 빈 스코프

본 글은 김영한님의 강의 내용을 바탕으로 정리한 글입니다.

 

스프링 핵심 원리 - 기본편 - 인프런 | 학습 페이지

지식을 나누면 반드시 나에게 돌아옵니다. 인프런을 통해 나의 지식에 가치를 부여하세요....

www.inflearn.com

목차

  1. 빈 스코프란?
  2. 프로토타입 스코프
  3. 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점
  4. 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결
  5. 웹 스코프
  6. request 스코프 예제 만들기
  7. 스코프와 Provider
  8. 스코프와 프록시

 

1. 빈 스코프란?

빈이 존재할 수 있는 범위, 생성부터 소멸까지의 범위를 가리키는데,

지금까지 싱글톤 스코프로 학습하였기 때문에, 스프링 컨테이너의 시작과 종료가 동일하게 유지되었었다.

빈이 가질 수 있는  다양한 스코프를 배워보자.

  • 기본 Bean
    • 싱글톤 : 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
    • 프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하는 짧은 범위의 스코프
  • 웹 Bean 
    • request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
    • session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
    • application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

 

지금까지 기본적으로 지원되는 싱글톤의 스코프를 보았으니, 프로토타입의 스코프 예시를 보자.

스코프 지정은 단순하다. @Scope("종류") 로 지정한다.

수동 / 자동 Bean 등록시 Scope 설정 예시

@Scope("prototype")
@Component
public class HelloBean {}
@Scope("prototype")
@Bean
PrototypeBean HelloBean() {
    return new HelloBean();
}

 

 

2. Prototype Scope

 

싱글톤 스코프의 빈을 조화하면 항상 같은 인스턴스를 반환하는 반면,

 

 

프로토타입 스코프의 빈을 조회하면, 조회 시점에 새로운 인스턴스를 생성하여 의존관계 주입후 반환해주고 소멸시킨다.

 

 

프로토타입 빈 스코프는 컨테이너가 생성 , 의존성 주입 , 초기화까지만 처리해준다.

그러므로 빈 생명주기 관리 어노테이션 중 @PostConstrutor는 수행이 가능하지만, @PreDestory 는 호출 불가하다.

 

싱글톤과 프로토타입 비교 Code

Singleton 테스트

package hello.core.scope;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import static org.assertj.core.api.Assertions.assertThat;
public class SingletonTest {
    @Test
    public void singletonBeanFind() {
        AnnotationConfigApplicationContext ac = new
                AnnotationConfigApplicationContext(SingletonBean.class);
        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(); //종료
    }

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

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

 

스프링 컨테이너에 2번의 빈 요청 모두 동일한 인스턴스를 반환해주었고,

종료시점에 Destroy를 수행했다.

 

Prototype 테스트

package hello.core.scope;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import static org.assertj.core.api.Assertions.*;
public class PrototypeTest {
    @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).isNotSameAs(prototypeBean2);
        ac.close(); //종료
    }
    @Scope("prototype")
    static class PrototypeBean {
        @PostConstruct
        public void init() {
            System.out.println("PrototypeBean.init");
        }
        @PreDestroy
        public void destroy() {
            System.out.println("PrototypeBean.destroy");
        }
    }
}

 

두번의 요청에 각각 다른 Bean을 생성하여 반환해주었고, 넘겨진후 스프링 컨테이너 종료시점에

Destroy를 호출해주지 못하고 끝났다.

 

프로토타입 빈의 특징 정리

  • 스프링 컨테이너에 요청할 때 마다 새로 생성된다.
  • 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입 그리고 초기화까지만 관여한다.
  • 종료 메서드가 호출되지 않는다.
  • 그래서 프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리해야 한다. 
  • 종료 메서드에 대한 호출도 클라이언트가 직접 해야한다.

 

 

3. Prototype Scope - 싱글톤 빈과 함께 사용시 문제점

package hello.core.scope;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class SingletonWithPrototypeTest1 {
    @Test
    void prototypeFind() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
        PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
        prototypeBean1.addCount();
        Assertions.assertThat(prototypeBean1.getCount()).isEqualTo(1);
        
        PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
        prototypeBean2.addCount();
        Assertions.assertThat(prototypeBean2.getCount()).isEqualTo(1);
    }
    @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");
        }
    }
}

프로토타입 빈을 생성할때,  count변수의 값을 올려주는 addCount를 수행하고 getCount로 받아오면,

항상 프로토타입은 빈을 생성해서 반환후 소멸시키기 때문에 그 값이 1이다.

그런데, 

싱글톤 빈과 혼용에서 사용하는 경우, 문제가 발생한다.

package hello.core.scope;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import static org.assertj.core.api.Assertions.*;
public class SingletonWithPrototypeTest1 {
    @Test
    void singletonClientUsePrototype() {
        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(2);
    }
    // 싱글톤 빈에서 프로토타입 빈을 생성해주면?
    static class ClientBean {
        private final PrototypeBean prototypeBean;
        @Autowired
        public ClientBean(PrototypeBean prototypeBean) {
            this.prototypeBean = prototypeBean;
        }
        public int logic() {
            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");
        }
    }
}

이렇게 ClientBean이라는 싱글톤 빈을 만들어서 그 안에서 프로토타입 빈을 생성시키면,

프로토 타입빈이 싱글톤 빈에 종속되므로, 컨테이너에서 소멸되지 않고 해당 프로토타입 인스턴스를 가지고 있게 된다.

따라서 프로토타입에서 항상 1을 기대하고 만들었다 할지라도, 싱글톤 빈안에서는 다른 결과를 내게 된다. 

 

 

4. Prototype Scope - 싱글톤 빈과 함께 사용시 Provider로 문제 해결

 

위와 같은 문제를 해결하는 3가지 방법을 소개한다.

 

1) 스프링 컨테이너 DL 

싱글톤 빈이 프로토타입을 사용할 때마다, 의도한대로 logic() 부분에서 프로토타입 빈을 새로 생성해주면 된다.

해당 메소드가 수행되는 시점에 프로토타입이 만들어졌다가, 종료시점에 프로토타입 빈이 소멸된다.

package hello.core.scope;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class PrototypeProviderTest {
    @Test
    void providerTest() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int count1 = clientBean1.logic();
        Assertions.assertThat(count1).isEqualTo(1);
        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int count2 = clientBean2.logic();
        Assertions.assertThat(count2).isEqualTo(1);
    }
    static class ClientBean {
        @Autowired
        private ApplicationContext ac; // 컨테이너 의존성 연결 = 의존관계 조회 DL (위험)
        public int logic() {
            // 로직호출시, 프로토타입 빈을 신규로 생성
            PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
            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");
        }
    }
}

각각의 다른 빈이 생성되었다가 잘 소멸되어, 1이라는 값을 유지해주었다.

하지만, 지금은 스프링 컨테이너에 의존성을 찾는 방법으로 싱글톤과 프로토타입빈 사이의 문제를 막아준것이다. 

이런 코드는 스프링 컨테이너를 종속하는 코드를 만든것이고, 

지금과 같은 테스트 경우가 아닌 개발에서는 매우 제약적이고 확장하기 어려운 코드가 된다. 

 

대신 스프링에서는 ObjectFactory, ObjectProvider 라는 서비스를 통해서

지금과 같이 의존하지 않고 DL기능을 제공해주므로 이러한 문제를 해결할 수 있다.

 

2) ObjectProvider 

지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것이 바로 ObjectProvider 이다. 

과거에는 ObjectFactory 가 있었는데, 여기에 편의 기능을 추가해서 ObjectProvider 가 만들어졌다.

@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
    PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
    prototypeBean.addCount();
    int count = prototypeBean.getCount();
    return count;
}

ObjectProvider 를 이용하면 스프링 컨테이너가 아닌 등록된 모든 프로토타입 빈을 쉽게 조회하여 사용 할 수 있다.

스프링이 제공하는 기능을 사용하지만, 기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워진다.

 

3) JSR-330 Provider

마지막 방법은 javax.inject.Provider 라는 JSR-330 자바 표준을 사용하는 방법이다.

이 방법을 사용하려면 javax.inject:javax.inject:1 라이브러리를 gradle에 추가해야 한다..

dependencies {
   // 프로바이더 사용을 위한 gradle추가
   implementation 'javax.inject:javax.inject:1'
   ...
}
@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
    PrototypeBean prototypeBean = provider.get();
    prototypeBean.addCount();
    int count = prototypeBean.getCount();
    return count;
}

 

 

  • 실행해보면 provider.get() 을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다.
  • provider 의 get() 을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다. (DL)
  • 자바 표준이고, 기능이 단순하므로 단위 테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워진다.
  • Provider 는 지금 딱 필요한 DL 정도의 기능만 제공한다.

특징

  • get() 메서드 하나로 기능이 매우 단순하다.
  • 별도의 라이브러리가 필요하다. (javax.inject)
  • 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.

 

정리

그러면 프로토타입 빈을 언제 사용할까? 

매번 사용할 때 마다 의존관계 주입이 완료된 새로운 객체가 필요하면 사용하면 된다. 

그런데 실무에서 웹 애플리케이션을 개발해보면, 

싱글톤 빈으로 대부분의 문제를 해결할 수 있기 때문에 프로토타입 빈을 직접적으로 사용하는 일은 매우 드물다.

ObjectProvider , JSR330 Provider 등은 프로토타입 뿐만 아니라 DL이 필요한 경우는 언제든지 사용할 수 있다.

 

> 참고: 스프링이 제공하는 메서드에 @Lookup 애노테이션을 사용하는 방법도 있지만, 이전 방법들로
충분하고, 고려해야할 내용도 많아서 생략함.

 

 

5. Web Scope

 

웹 스코프의 특징

  • 웹 스코프는 웹 환경에서만 동작한다.
  • 웹 스코프는 프로토타입과 다르게 스프링이 해당 스코프의 시작부터 종료시점까지 관리한다. 
  • 따라서 종료 메서드가 호출된다.

웹 스코프 종류

  • request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 요청마다 별도의 빈 인스턴스가 생성된다.
  • sessionHTTP Session과 동일한 생명주기를 가지는 스코프
  • application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프
  • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

사실 세션이나, 서블릿 컨텍스트, 웹 소켓 같은 용어를 잘 모르는 분들도 있을 것이다. 

여기서는 request 스코프를 예제로 설명하겠다. 나머지도 범위만 다르지 동작 방식은 비슷하다.

 

 

6. request 스코프 예제 만들기


// 웹 스코프 사용을 위한 Gradle 추가
implementation 'org.springframework.boot:spring-boot-starter-web'

웹 Gradle을 추가하고 hello.core 밑에 CoreApplication에 main메소드를 실행해보면,

내장된 톰캣서버가 실행됨을 확인 할 수 있다.

  • 참고: 스프링 부트는 웹 라이브러리가 없으면 우리가 지금까지 학습한 AnnotationConfigApplicationContext 을 기반으로 애플리케이션을 구동한다. 
  • 웹 라이브러리가 추가되면 웹과 관련된 추가 설정과 환경들이 필요하므로
    AnnotationConfigServletWebServerApplicationContext 를 기반으로 애플리케이션을 구동한다

만약 기본 포트인 8080 포트를 다른곳에서 사용중이어서 오류가 발생하면 포트를 변경해야 한다. 

 

예시 ) 9090 포트로 변경하려면 main/resources/application.properties 에서 다음 설정을 추가하자.

server.port=9090

request 스코프 예제 개발

  • 동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵다.
  • 이럴때 사용하기 딱 좋은것이 바로 request 스코프이다.
  • 다음과 같이 로그가 남도록 request 스코프를 활용해서 추가 기능을 개발해보자.
[d06b992f...] request scope bean create
[d06b992f...][http://localhost:8080/log-demo] controller test
[d06b992f...][http://localhost:8080/log-demo] service id = testId
[d06b992f...] request scope bean close
  • 기대하는 공통 포멧: [UUID][requestURL] {message}
  • UUID를 사용해서 HTTP 요청을 구분하자.
  • requestURL 정보도 추가로 넣어서 어떤 URL을 요청해서 남은 로그인지 확인하자.
package hello.core.common;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.UUID;

@Component
@Scope(value = "request")
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);
    }
}
package hello.core.logdemo;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final MyLogger myLogger;

    public void logic(String id) {
        myLogger.log("service id = " + id);
    }
}
package hello.core.logdemo;
import hello.core.common.MyLogger;
import hello.core.logdemo.LogDemoService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequiredArgsConstructor
public class LogDemoController {
    private final LogDemoService logDemoService;
    private final MyLogger myLogger;
    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        String requestURL = request.getRequestURL().toString();
        myLogger.setRequestURL(requestURL);
        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
    }
}

mvc 패턴을 가지는 컨트롤러와 서비스부분을 생성해주었다.

의도대로 정상기동 된다면, http://localhost:8080/log-demo 로 접속했을때, 

Controller에 의해 화면에는 OK가 반환되어 랜더링 되고, logic 구현 내용과 같이 로그가 남을 것이다.

 

하지만, 실행해보면 에러가 발생하면서 서버가 뜨지 않는다.

Error creating bean with name 'myLogger': Scope 'request' is not active for the
current thread; consider defining a scoped proxy for this bean if you intend to
refer to it from a singleton;

이유는 서버가 기동되면서 @Service 컴포넌트가 빈으로 등록될 때,

request의 스코프를 가지는 MyLogger 클래스는 생성되지 않기 때문에, 

생성자 호출에서 주입받지 못하고 에러가 발생한 것이다.

 

로그를 살펴보면, 프록시 스코프를 이용하라는 힌트를 주고 있는데, 

이 문제를 ProviderProxy 두 가지 방법으로 해결해 볼 수 있다.

 

 

 

7. 스코프와 Provider

 

 

package hello.core.web;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.inject.Provider;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequiredArgsConstructor
public class LogDemoController {
    private final LogDemoService logDemoService;
    private final Provider<MyLogger> provider;
    //private final MyLogger myLogger;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        MyLogger myLogger = provider.get();
        System.out.println("myLogger = " + myLogger.getClass()); //프록시 객체사용 출력
        String requestURL = request.getRequestURL().toString();
        myLogger.setRequestURL(requestURL);
        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
    }
}
package hello.core.web;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.inject.Provider;

@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final Provider<MyLogger> provider;
    //private final MyLogger myLogger;
    public void logic(String id) {
        MyLogger myLogger = provider.get();
        myLogger.log("service id = " + id);
    }
}

서비스와 컨트롤러에서 MyLogger 호출부를

Provider로 바꾸어 get을 통해 받아오도록 변경해주면,

서비스 기동시점에 Provider를 생성자로 가지고 주입받기 때문에 

request scope 빈의 생성을 지연시켜주어

이상없이 동작한다.

 

 

8. 스코프와 프록시

 

Provider를 주입받지 않고, MyLogger 를 그대로 생성자 주입받는 방법도 있다.

Error creating bean with name 'myLogger': Scope 'request' is not active for the
current thread; consider defining a scoped proxy for this bean if you intend to
refer to it from a singleton;

처음 문제가 발생했을 당시 로그를 잘 읽어보면,

request 스코프의 MyLogger가 생성되지 않았기에 Scoped Proxy를 고려해보라고 가이드하고 있다.

웹 스코프를 가지는 빈은 ProxyMode를 설정해줄 수 있는데,

이를 통해 초기 빈 등록시점에 가짜 (프록시)클래스를 생성하여 주입해주므로

진짜(MyLogger) 클래스의 생성을 지연시켜줄 수 있는 기술이다.

package hello.core.common;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.UUID;

@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);
    }
}
package hello.core.web;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.inject.Provider;

@Service
@RequiredArgsConstructor
public class LogDemoService {
    //private final Provider<MyLogger> provider;
    private final MyLogger myLogger; // 처음 상태 그대로
    public void logic(String id) {
        //MyLogger myLogger = provider.get();
        myLogger.log("service id = " + id);
    }
}
package hello.core.web;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.inject.Provider;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequiredArgsConstructor
public class LogDemoController {
    private final LogDemoService logDemoService;
    private final MyLogger myLogger;
    //private final Provider<MyLogger> provider;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        //MyLogger myLogger = provider.get();
        System.out.println("myLogger = " + myLogger.getClass()); //프록시 객체사용 출력
        String requestURL = request.getRequestURL().toString();
        myLogger.setRequestURL(requestURL);
        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
    }
}

프록시 객체를 찍는 부분을 살펴보면, 

우리가 만든 객체가 아닌 내부적으로 CGLIB 라는 가짜 클래스가 사용되었음을 확인 할 수 있다.

이는 과거 싱글톤을 보장하는 방법으로 @Configuration을 이용해 빈을 등록했을때에도 사용했던것과 같은 문구이다.

 

웹스코프를 가지는 빈의 경우 이렇게 ProxyMode를 이용해서

생성자 지연을 할 수 있고, 웹스코프 빈 등록 문제를 해결할 수 있다. 

 

 

소스코드 ↓