싱글톤 빈 요청과 다르게, 프로토타입 빈 요청은 요청마다 새로운 프로토타입 빈을 생성하고 필요한 의존관계를 주입한다.
클라이언트에 전달 후 반환 및 관리는 하지 않는다.
스프링 컨테이너는 프로토타입 빈을 생성, 의존관계 주입, 초기화까지만 처리한다.
프로토타입 빈을 관리할 책임은 클라이언트에 있기 때문에 @PreDestroy 등의 종료 메서드가 호출되지 않는다.
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 SingletonTest {
@Test
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);
Assertions.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");
}
}
}
싱글 톤 빈
같은 객체가 생성되고, Close도 정상적으로 진행된다.
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;
import static org.assertj.core.api.Assertions.*;
public class PrototypeTest {
@Test
void prototypeBeanFind(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
System.out.println("find prototypeBean1");
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class); // init
System.out.println("find prototypeBean2");
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class); // init
System.out.println("prototypeBean1 = " + prototypeBean1);
System.out.println("prototypeBean2 = " + prototypeBean2);
assertThat(prototypeBean1).isNotSameAs(prototypeBean2); // true
// prototypeBean1.destroy();
// prototypeBean2.destroy();
ac.close(); // 종료 메서드 실행 X
}
@Scope("prototype")
static class PrototypeBean{
@PostConstruct
public void init(){
System.out.println("PrototypeBean.init");
}
@PreDestroy
public void destroy(){
System.out.println("PrototypeBean.destroy");
}
}
}
프로토타입 빈
두 객체가 다르다. 클라이언트가 직접 destroy()를 해주지 않는 이상, close가 실행되지 않는다.
DL(Dependency Lookup) : 직접 필요한 의존관계를 찾는 것
ObjectFactory : 기능이 단순, 스프링 의존
ObjectProvider : getObject()를 호출하면 내부에서 스프링 컨테이너를 통해 빈을 찾아 반환한다.
웹 스코프
웹 환경에서만 동작
스프링이 종료시점까지 관리한다. 종료 메서드가 호출됨
- request
- session
- application
- websocket
HTTP request 요청 당 할당되는 request 스코프는 사용자마다 다르다.
request 스코프 예제 개발
목적 : 다양한 HTTP request 요청에 대해서,
이렇게 로그가 남도록 추가 기능을 개발해보자 (UUID를 사용해서 HTTP 요청을 구분한다.)
@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";
}
}
log-demo 페이지에 이동하면 아래에 있는 logDemo 함수가 실행되도록 하였다.
요청을 받아와 URL 을 저장하고, myLogger의 객체에 저장한다.
@Component
@Scope(value = "request")
public class MyLogger {
private String uuid; // random 생성, request를 구분.
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);
}
}
MyLogger 클래스에서 uuid는 요청의 식별번호, requestURL은 받아온 URL을 의미한다.
이 클래스를 기반으로 Log를 남기는 Controller와 Service를 설계할 것이다.
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id) {
myLogger.log("service id = " + id);
}
}
HTTP가 Service 까지 넘어오지 않도록 MyLogger클래스를 이용해 request의 ID를 넘긴다.
이렇게 설계한 계층에서, request가 실제로 들어오지 않았으므로 myLogger 객체는 동작할 수 없다.
이 경우, ObjectProvider를 이용해 실제로 HTTP가 들어온 상황을 만들어 request 스코프를 사용가능하게 만든다.
CGLB라는 라이브러리로 가짜 프록시 객체를 만들어 주입한다.
myLogger이름으로 가짜 프록시 객체를 스프링 컨테이너에 등록, 이 가짜 객체가 myLogger.logic()을 호출
request scope과 관련이 없고 single ton처럼 동작한다.
이 덕분에 편리하게 request scope를 사용할 수 있고, 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 저이 중요하다. 원본 객체를 가짜 프록시 객체로 대체, 다형성과 DI 컨테이너가 가진 강점이라고 한다.
'Framework > Spring' 카테고리의 다른 글
[IBAS] Java 제네릭(Generic) 을 이용한 코드 리팩토링 (0) | 2022.02.12 |
---|---|
MockMVC를 이용한 BoardService 단위 테스트 (JUnit5) (0) | 2022.01.21 |
인프런 스프링 핵심 원리-기본편 #5 싱글톤 컨테이너 / 의존관계 자동 주입 (0) | 2022.01.05 |
인프런 스프링 핵심 원리-기본편 #4 스프링 컨테이너와 스프링 빈 (0) | 2022.01.04 |
인프런 스프링 핵심 원리-기본편 #3 스프링 핵심원리 2 - 객체 지향 원리 적용 (0) | 2022.01.03 |