웹 환경에서만 동작하는 빈 스코프이다.
Table of Content
웹 스코프의 종류
•
“request” : HTTP 요청 하나가 들어 오고 나갈 때 까지 유지되는 스코프, 요청마다 별도 빈 인스턴스가 생성되고 관리된다.
•
“session” : HTTP Session과 동일한 생명주기를 가지는 스코프
•
“application” : 서블릿 컨텍스트와 동일한 생명주기를 가지는 스코프
•
“websocket” : 웹 소켓과 동일한 생명주기를 가지는 스코프
세션, 어플리케이션, 웹소켓은 범위만 다르지만 동작방식은 비슷하다. 대표적으로 request 스코프의 예시를 참고하고자 한다.
0. web 실행 환경
예시를 위해서는 웹 환경에서 동작해야 하므로 build.gradle에 web 라이브러리를 설치해야 한다.
dependencies {
...
//Web 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-web'
...
}
Java
복사
request 스코프
HTTP 요청과 응답까지의 생명주기와 함께하게 된다.
HTTP 요청에 대한 로그를 남기는 예시를 작성해보고자 한다. 동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵다. 이럴때 사용하기 적합한 것이 request 스코프이다. 요청의 UUID를 사용해서 HTTP 요청을 구분하고자 한다.
로그를 출력하기 위한 MyLogger.java 클래스 코드
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 messege) {
System.out.println("[" + uuid + "]" + "[" + requestURL + "] " + messege);
}
@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);
}
}
Java
복사
@Scope(value = "request") 를 통해 request 스코프로 지정했다. uuid를 통해 요청의 고유값을 확인 할 수 있도록 활용했다.
위 클래스를 사용하기 위한 controller, service를 작성해준다.
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.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";
}
}
Java
복사
package hello.core.web;
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);
}
}
Java
복사
서버를 실행해보면 다음과 같은 오류가 발생한다.
MyLogger가 request 스코프로 설정되어 해당 빈이 활성화되지 않은것에 대한 오류가 나타난다. request가 없는 상태에서 해당 빈을 찾으려고 하기 때문이다 request 스코프의 빈 생성은 실제 request가 와야 생성 된다.
Provider로 위 문제 해결
ObjectProvider를 사용하지 않고, 스프링 컨테이너가 로딩되는 시점에 자동의존관계주입(Autowired)을 위해 Request Scope 빈을 생성하려면 Scope가 맞지 않아서 오류가 발생한다.
그래서 ObjectProvider를 사용하여 요청이 들어와서 Request Scope이 필요한 시점에 ObjectProvider로 Request Scope 빈을 찾아와 (찾아봤는데 없으면 생성) 사용할 수 있게 할 수 있다.
ObjectProvider로 변경한 LogDemoController
package hello.core.web;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
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 ObjectProvider<MyLogger> myLoggerObjectProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
String requestURL = request.getRequestURL().toString();
MyLogger myLogger = myLoggerObjectProvider.getObject();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
Java
복사
ObjectProvider로 변경한 LogDemoService
package hello.core.web;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final ObjectProvider<MyLogger> myLoggerObjectProvider;
public void logic(String id) {
MyLogger myLogger = myLoggerObjectProvider.getObject();
myLogger.log("service id = " + id);
}
}
Java
복사
이후 브라우저를 통해 URL에 요청을 보내게 되면
동일한 UUID HTTP 요청안에서만 MyLogger의 주소값이 같은 동일한 빈이 사용되는 것을 볼 수 있다. HTTP 요청 마다 다른 로거 인스턴스가 생성되고 있다. (request 스코프) 여러 요청이 들어와도 각 요청마다 인스턴스가 생성, 관리되고 있다.
Proxy로 위 문제 해결
하지만 ObjectProvider로 처리하면 코드가 상당히 늘어나기 때문에 좀 더 간결하게 만들기 위해 Proxy를 사용할 수 있다.
MyLogger 클래스에 proxyMode 속성을 추가한다. 이를 통해 가짜(대리) 클래스가 만들어지고 request와 상관 없이 프록시 클래스를 다른 빈에 미리 주입 할 수 있다.
@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 messege) {
System.out.println("[" + uuid + "]" + "[" + requestURL + "] " + messege);
}
@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);
}
}
Java
복사
Controller, Service 모두 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.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";
}
}
Java
복사
package hello.core.web;
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);
}
}
Java
복사
Proxy 설정 이전에는 HTTP 요청이 없기때문에 request 스코프 빈을 찾지 못했지만 Proxy를 통해 MyLogger를 상속 받은 가짜 프록시 객체를 미리 주입하게 된다. 요청으로 실제 빈을 요청하게 되면 가짜 프록시 객체가 실제 객체를 조회하게 되는 것이다.
•
프록시 객체 덕분에 request 스코프 빈을 마치 싱글톤 빈을 사용하는것 처럼(실제 내부적으로는 싱글톤이 아닌점에서 주의) 간편하게 사용 가능
•
Provider나 Proxy나 진짜 객체 조회를 필요한 시점까지 지연(lazy) 로딩 한다는 점이 핵심
•
애너테이션 설정만으로 프록시 객체로 변경 할 수 있는 다형성, DI 컨테이너의 강점이다.
•
== 클라이언트 코드를 수정하지 않을 수 있다.
Related Posts
Search