Spring Web Scope

강정우·2023년 11월 20일
0

Spring-boot

목록 보기
23/73

웹 스코프

웹 스코프의 특징

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

웹 스코프의 종류

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

  1. session : HTTP session 과 동일한 생명주기를 갖는 스코프
  2. application : ServletContext와 동일한 생명주기를 갖는 스코프
  3. websocket : 웹 소켓과 동일한 생명주기를 갖는 스코프

웹 환경 추가

  • 웹 스코프는 웹 환경에서만 동작하므로 web 환경이 동작하도록 라이브러리를 추가하자.

  • build gradle에 아래 코드를 추가해줘서 프로젝트.이름.CoreApplication의 main 메서드를 실행하면 웹 어플리케이션이 실행되는 것을 확인할 수 있다.

implementation 'org.springframework.boot:spring-boot-starter-web'
  • spring-boot-starter-web 라이브러리르 추가하면 스프링 부트는 내장 톰켓 서버를 활용해서 웹 서버와 스프링을 함께 실행시킨다.

  • 스프링 부트는 웹 라이브러리가 없으면 우리가 지금까지 학습한 AnnotationConfigApplicationContext를 기반으로 어플리케이션을 구동한다. 웹 라이브러리가 추가되면 웹과 관련되 추가 설정과 환경들이 필요하므로 AnntationConfigServletWEbServerApplicationContext를 기반으로 어플리케이션을 구동한다.

request 스코프

  • 동시에 여러 HTTP req가 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵다.
    이럴때 사용하기 좋은것이 바로 request 스코프이다.
@Component
@Scope("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() {
        uuid = UUID.randomUUID().toString();
        System.out.println("["+uuid+"] request scope bean create: "+this);
    }

    @PreDestroy
    public void close() {
        System.out.println();
        System.out.println("[" + uuid + "] request scope bean close: " + this);
    }
}
  • 위 코드는 로그를 출력하기위한 간단한 클래스를 한 번 만들어봤다.
    로그 출력 포멧 : [UUID][requestURL]{message}

  • UUID를 사용해서 HTTP req를 구분하였다.
    즉, 같은 req이다. 즉, 같은 사용자의 것인 것을 확인할 수 있따.

  • @Scope(value="request")를 사용해서 request 스코프롤 지정하였다. 물론이때 위 코드처런 value를 생략해도 무관하다.

  • HTTP req당 하나씩 생성되고, HTTP req가 끝나는 시점에 소멸된다.

  • 이 빈이 생성되는 시점에 자동으로 @PostConstruct 초기화 메서드를 사용해서 uuid를 생성해서 저장해둔다. 이 빈은 HTTP req당 하나씩 생성되므로, uuid를 저장해두면 다른 HTTP 요청과 구분할 수 있다.

  • requestURL 필드값은 이 빈이 생성되는 시점에는 알 수 없으므로, 외부에서 setter로 입력받도록 하였다.

  • 그럼 이제 이어서 request scope를 테스트 할 자바 클래스를 만들어보자.
    일단 서비스 로직을 대강 만들어주고

@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final MyLogger myLogger;
    public void logic(String testId) {
        myLogger.log("service id = " + testId);
    }
}
  • 이때, 참고로 request scope를 사용하지 않고 파라미터로 이 모든 정보를 서비스 계층에서 넘겨받는다면 단점이 생긴다.
  1. 파라미터가 많아서 지저분해진다.
  2. requestURL 같은 웹과 관련된 정보가 웹과 관련없는 서비스 계층까지 넘어가게된다.
    웹가 관련된 부분은 컨트롤러까지만 사용해야한다.
  3. 서비스 계층은 웹 기술에 종속되지 않고, 가급적 순수하게 유지하는 것이 유지보수 관점에서 좋다.
    reqeust scope의 MyLogger 덕분에 이런 부분에 파라미터로 넘기지 않고, 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.getRequestURL().toString();
        myLogger.setRequestURL(requestURL);
        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
    }
}
  • HttpServeletReqeust 타입은 자바에서 제공하는 표준 servelt 규약이 있는데 그에 의한 http-req 정보를 받을 수 있다. 즉, 교객 요청 정보를 받을 수 있다.

  • 컨트롤러에 위에서 만든 서비를 붙였다. 그럼 위 코드를 실행시켜보면 어떻게 될까?
    -> 에러가 나야 정상이다.

  • 대강 request 스코프의 생명 범위는 사용자가 req를 날려야하는데 spring project가 뜰 때엔 당연히 사용자의 req가 없기 때문에 없는 의존성(빈)을 달라고 해서 에러가 난 것이다.
    그래서 여기서 또 앞서 배운 필요할 때 찾는 DL을 사용하는 Provider를 사용하면 된다.

참고
reqeustURL을 MyLogger에 저장하는 부분은 컨트롤러보다는 공통 처리가 가능한 스프링 인터셉터나 서블릿 필터같은 곳을 활용하는 것이 더 좋다.

Provider 적용

  • 앞서 배운 것 처럼 각각의 서비스와 컨트롤러에 Provider를 붙여주면
@Controller
@RequiredArgsConstructor
public class LogDemoController {
    private final LogDemoService logDemoService;
    private final ObjectProvider<MyLogger> myLoggerProvider;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        String requestURL = request.getRequestURL().toString();
        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.setRequestURL(requestURL);
        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
    }
}

  • 그래서 현업에서는 아이디를 서비스 넘길 때 같이 넘기고 같이 묶어서 로그를 남겨주면 이제 필터링을 해서 보기 편하다.

  • 간단하게 다시 설명하자면 MyLogger myLogger = myLoggerProvider.getObject(); 즉 초기화를 요청했을 때 만들어지면서 @PostConstruct에서 HTTP req랑 내 req랑 연결을 시키면서 uuid랑 걸리도록 한다. 그 다음 setter로 값을 넣어준다.

  • 참고로 ObjectProvider.getObject()를 위처럼 Controller에서 호출하든 아니면 아래처럼 Service에서 호출하든 같은 HTTP req라면 같은 스프링 빈이 반환된다.

@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final ObjectProvider<MyLogger> myLoggerProvider;
    public void logic(String testId) {
        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.log("service id = " + testId);
    }
}
  • 그런데 이 Provider를 사용하지 않고도 더 좋은 방법이 있다. 즉, 필드값을 최초에 생성한 대로 두고 작성하는 방법이 있다는 것이다. 이는 바로 Proxy를 이용하면 된다.
profile
智(지)! 德(덕)! 體(체)!

0개의 댓글