728x90
빈 스코프 란?
- 지금까지 스프링 빈이 스프링 컨테이너의 시작과 함께 생성되어 스프링 컨테이너가 종료될 때 까지 유지된다고 알고있었다. 이것은 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문이다. 스프링은 싱글톤 스코프 말고도 다양한 스코프를 지원한다.
- 빈 스코프 종류
- 싱글톤 : 기본 스코프, 스프링 컨테이너의 시작부터 종료까지 유지되는 가장 넓은 범위의 스코프
- 프로토타입 : 스프링 컨테이너 프로토타입 빈의 생성과 의존 관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
- 웹 관련 스코프
- request : 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
- session : 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
- application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
프로토타입 스코프
- 스프링 컨테이너가 항상 새로운 인스턴스를 생성해서 반환한다.
- 스프링 컨테이너는 프로토타입 빈을 생성하고 의존 관계 주입과 초기화까지만 처리하고 이후에는 프로토타입 빈을 관리하지 않는다(@PreDestroy와 같은 종료 메서드가 호출되지 않음). 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있다.
package com.example.core.scope;
import org.junit.jupiter.api.DisplayName;
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 PrototypeTest {
@Test
@DisplayName("프로토타입 빈 테스트")
public void prototypeBeanFind() throws Exception {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(PrototypeBean.class);
System.out.println("find prototypeBean1");
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
System.out.println("find prototypeBean2");
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");
}
}
}
/*
출력 결과
**find prototypeBean1
PrototypeBean init
find prototypeBean2
PrototypeBean init
prototypeBean1 = com.example.core.scope.PrototypeTest$PrototypeBean@5bf0fe62
prototypeBean2 = com.example.core.scope.PrototypeTest$PrototypeBean@1e097d5
*/**
- 프로토타입 스코프는 싱글톤 스코프와 다르게 빈을 조회할 때 생성되고 초기화 메서드도 실행
- 위 코드에서 프로토타입 빈을 2번 조회했으므로 다른 빈 2개가 생성되고 초기화도 2번 실행이 된다.
- 하지만 프로토타입 빈은 스프링 컨테이너가 빈 생성과 초기화 까지만 관리하기 때문에 스프링 컨테이너가 종료될 때 소멸 작업이 자동으로 되지 않는다. 만약 소멸 작업을 하고 싶으면 직접 이 코드를 사용하여 prototypeBean2.destroy(); 소멸을 시켜줘야한다.
웹 스코프
- 웹 환경에서만 동작한다.
- 프로토타입 스코프와는 다르게 스프링이 해당 스코프의 종료시점까지 관리한다.
- 웹 스코프의 종류
- request : HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다.
- session : HTTP Session과 동일한 생명주기를 가지는 스코프
- application : ServletContext와 동일한 생명주기를 가지는 스코프
- websocket : 웹 소켓과 동일한 생명주기를 가지는 스코프
request scope 실습
- 동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어려운데 이럴때 쓰기 좋은것이 request scope이다.
- request scope를 사용하지 않고 파라미터로 이 모든 정보를 서비스 계층에 넘기면 파라미터가 많아져서 코드가 지저분해진다. 서비스 계층은 가급적이면 순수하게 유지하는 것이 유지보수 관점에서 좋다.
- request scope을 사용하면 파라미터로 넘기지 않고 서비스 계층에서 멤버변수에 저장해서 웹과 관련된 정보를 사용 할 수 있어지므로 서비스 계층을 순수하게 유지가 가능하다.
// scope
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
// proxyMode = ScopedProxyMode.TARGET_CLASS
// 위 코드를 추가해 MyLogger의 가짜 프록시 클래스를 만들어 두고
// 실제 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입 가능
// 위 부분을 적용해주지 않으면 서버 실행시 에러가 난다.
@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("[" + this.uuid + "]" + " [" + this.requestURL + "]"
+ message);
}
@PostConstruct
public void init() {
this.uuid = UUID.randomUUID().toString();
System.out.println("[" + this.uuid + "] request scope bean create" + this);
}
@PreDestroy
public void close() {
System.out.println("[" + this.uuid + "] request scope bean close" + this);
}
// controller
import com.example.core.common.MyLogger;
import com.example.core.logdemo.service.LogDemoService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
private final MyLogger myLogger;
@GetMapping("/log-demo")
public String logDemo(HttpServletRequest request) {
String requestURL = request.getRequestURL().toString();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
// service
import com.example.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);
}
}
728x90
'BackEnd > Spring' 카테고리의 다른 글
@ControllerAdvice와 @ExceptionHandler (0) | 2023.01.08 |
---|---|
DispatcherServlet과 Spring MVC 아키텍처 (0) | 2022.12.18 |
빈 생명주기 콜백 (0) | 2022.12.18 |
의존 관계 자동 주입 (0) | 2022.12.18 |
싱글톤 컨테이너 (0) | 2022.12.18 |