[Spring] 빈 스코프 - 웹 스코프, Request 스코프

[Spring] 빈 스코프 - 웹 스코프, Request 스코프

웹 스코프

웹 환경에서만 동작

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

종류

request: HTTP 요청 하나가 들어오고 나갈때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다.

session: HTTP Session과 동일한 생명주기를 가지는 스코프

application: 서블릿 컨텍스느와 동일한 생명주기를 가지는 스코프

websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

동시에 요청할 경우 각가 다른 Bean이 생성되고 관리된다.

Request 스코프 예제만들기

build.gradle에 추가

implementation 'org.springframework.boot:spring-boot-starter-web'

위 라이브러리를 추가하면 톰캣 포트가 추가되며 웹 어플리케이션이 작동한다.

웹 라이브러리를 추가하면 AnnotationConfigApplicationContext를 기반으로 애플리케이션을 구동하지않고

AnnotationConfigServletWebServerAplicationContext를 기반으로 애플리케이션을 구동한다.

여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵기 때문에 Request스코프를 사용해서 구분

기대하는 공통포맷: [UUID][RequestUrl]{Message}

UUID를 사용해서 HTTP 요청을 구분

reqeustUrl 정보도 추가로 넣어서 어떤 URL을 요청해서 남은 로그인지 확인

@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() { String 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); } }

url은 생성되는 시점에서 알 수 없으므로 외부에서 setter로 지정해준다.

@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.getRequestURI().toString(); myLogger.setRequestURL(requestURL); myLogger.log("controller test"); logDemoService.logic("testId"); return "OK"; } }

Controller를 작성하고 실행시키게 되면 오류가 발생한다

MyLogger는 request 스코프로 지정된 빈인데 웹 관련 요청이 없어서 생성 자체가 안되기 떄문에 어플리케이션

실행되면서 오류가 발생한다. MyLogger를 빈으로 등록하려면 어플리케이션 실행할때는 생성하지 않다가 실질적으로 요청이 들어왔을때 빈을 조회 및 생성해야한다.

스코프와 Provider

@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"; } // [b0969b6e-1287-42b1-bc5b-688715830a78] request scope bean create: hello.core.common.MyLogger@1f7962b6 // [b0969b6e-1287-42b1-bc5b-688715830a78] [/log-demo]controller test // [b0969b6e-1287-42b1-bc5b-688715830a78] [/log-demo]service id: testId // [b0969b6e-1287-42b1-bc5b-688715830a78] request scope bean close: hello.core.common.MyLogger@1f7962b6

myLogger를 ObjectProvider를 사용하여 request가 왔을때에만 DL하여 로그를 출력할 수 있다.

스코프와 프록시

@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS) public class MyLogger {

@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.getRequestURI().toString(); myLogger.setRequestURL(requestURL); myLogger.log("controller test"); logDemoService.logic("testId");

@Service @RequiredArgsConstructor public class LogDemoService { private final MyLogger myLogger; public void logic(String id) { myLogger.log("service id: " + id); } }

위 예제와 달리 proxyMode = ScopedProxyMode.TARGET_CLASS를 @Scope 애노테이션 내에 지정해주면

Provider를 사용하지 않아도 잘 동작한다.

로그를 찍어보면

myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$24131e0b와 같이 나오는데 여기의 MyLogger는 스프링이 조작해서 생성한 myLogger를 반환함을 알 수 있다.

이런 경우 MyLogger의 가짜 프록시 클래스를 만들어 두고 실제 기능호출을 할 경우 진짜 빈을 생성해서 가져온다.

Provider든 프록시든 핵심은 진짜 객체를 조회하기 전까지 지연처리하는것

단지 애노테이션 설정 변경만으로 원본객체를 프록시 객체로 대체할 수 있는 것이다.

대상이 Class 일 경우: ScopedProxyMode.TARGET_CLASS

대상이 Interface 일 경우 : ScopedProxyMode.INTERFACES

from http://ju-bong.tistory.com/41 by ccl(A) rewrite - 2021-12-23 22:00:53