web +a

웹 스코프 | request scope

냥냥체뤼 2022. 7. 15. 11:18

웹 환경에서만 동작하는.. 웹 스코프.. 공부시쟉 ✨

 

 


● 웹 스코프 종류

 

request

: HTTP 요청 하나가 들어오고 나갈 때까지 유지되는 스코프

: 각각의 HTTP 요청마다 별도의 빈 인스턴스 생성&관리

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

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

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

↑ 나중에 공부하자

 

 


 web 라이브러리 추가

 

웹 스코프인 request 스코프를 공부하기 위해서 웹 라이브러리를 추가해야 한다.

 

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

 

그러면 CoreApplication (main메서드) 실행시 이전과 달라진 점

: 톰캣 서버가 뜬다. localhost:8080 접속 가능!

: 스프링부트는 내장 톰캣서버를 활용하여 웹서버와 스프링을 함께 실행시킨다.

 

↓ 톰캣서버가 떴다는 로그를 확인 가능하다.

main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''

↓ localhost:8080에 접속 가능하다. 

 

더보기

※ 스프링 부트는

웹 라이브러리가 없으면 

- AnnotationConfigApplicationContext 기반으로 애플리케이션을 구동함

웹 라이브러리가 추가되면

- AnnotationConfigServletWebServerApplicationContext 기반으로 애플리케이션을 구동함

- 웹과 관련된 추가 설정 & 환경이 필요하기 때문이다.

 


request 스코프 예제

 MyLogger 구현 --- HTTP 요청별 로그 구분하기

 

 

동시에 여러 쓰레드에서 HTTP 요청이 올때 로그를 구분할 필요가 있다. 이럴때  request 스코프를 사용한다.

request 스코프를 활용하여 사용자의 로그를 남기는 기능을 구현해보자.

 

 

 

∨ 로그를 출력하기 위한 MyLogger 클래스 구현

∨ 요구사항 : 

- 포맷 : [UUID][requestURL]{message}

- UUID를 통해 HTTP 요청이 구분된다

- 어떤 URL을 요청해서 남은 로그인지 확인 : requestURL 정보

 

cf. 로그는 어디서나 확인할 수 있는 공통사항이기에 common 패키지에 넣었다

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;


    // requestURL은 외부에서 setter로 받도록 한다.
    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 = 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을 가지고 있다.

- uuid는 생성시점에 부여되고

- requestURL은 어디선가 setter를 통해 넣어줄 수 있다.

=> 사용자로부터 받은 URL 요청을 처리하는 (프레젠테이션 층의) Controller에서 MyLogger의 setter를 사용해 URL을 넣어주면 되겠구나~~! (>v <*) /

 

=> 라고 생각했지만~~~~ ㅠㅅ ㅠ 실제론 그러지 말라고 바로 정정해주신다... ↓↓

넵!! 알겠습니다!! e(>v <*)/

 

 

 

 


 컨트롤러와 서비스

자 이제 웹을 구성하기 위해

MyLogger를 멤버변수로 가지는 Controller와 Service를 구현해보자.

recall) 웹 애플리케이션의 구조 : https://cherryjubilee.tistory.com/5

 

∨ Controller

더보기
@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";
    }
}

- Service와 Logger를 멤버변수로 가진다.

- @RequestMapping("log-demo") & HttpServletRequest를 통해서 요청 URL을 받는다.

=> Logger에 http://localhost:8080/log-demo를 setRequestURL하게 된다.

- 컨트롤러라는 것을 표시하기 위해 Logger에 "controller test"라는 log를 남기도록 했다.

- 어떤 서비스 로직에 testID를 String으로 건네주었다.

 

 

∨ Service

더보기
@Service
@RequiredArgsConstructor
public class LogDemoService {

    private final MyLogger myLogger;

    public void logic(String id) {
        myLogger.log("service id = " + id);
    }
}

- 그냥 어떤 서비스로직이다. 

- 뭐.. 그냥.. log를 찍게 했다..!

 

 

 


 request scope 빈 사용시 주의할 점

 

Controller는 스프링 컨테이너가 뜰 때 생성된다. 

위 Controller는 MyLogger를 멤버로 가지고 있다.

문제는 멤버인 MyLogger가 사용자의 HTTP 요청이 뜰 때 생성되는 request scope라는 것이다.

(request scope의 생명주기 : 고객요청 들어오고 나가기까지)

Controller 생성시점에 MyLogger는 존재하지 않기 때문에 위 예제처럼만 구현하면 문제가 된다!

 

더보기

Caused by: org.springframework.beans.factory.support.ScopeNotActiveException: 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; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

 

해결방법 :

1. 이전에 배운 Provider를 적용한다.

2. 프록시 방식을 사용한다.

 

 

△ 바로 다음 학습으로 두가지 방식을 실습해보자 >ㅅ <

 

 

 

반응형