Dispatcher Servlet

정민주·2024년 2월 12일

스프링 스터디

목록 보기
8/17

🤔디스패처 서블릿

: 디스패처 서블릿의 dispatch는 "보내다"라는 뜻을 가지고 있습니다.

그리고 이러한 단어를 포함하는 디스패처 서블릿은 HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 "프론트 컨트롤러" 라고 정의할 수 있습니다.

프론트 컨트롤러?
: 프론트 컨트롤러(Front Controller)란 서블릿 컨테이너의 제일 앞에서 서버로 들어오는 클라이언트의 모든 요청을 받아서 처리해주는 컨트롤러이다.

프론트 컨트롤러가 도입되기 전에는 각 컨트롤러마다 공통 로직을 복붙 형식으로 다시 작성하여 사용했지만, 프론트 컨트롤러가 도입된 이후에는 공통의 로직에 대한 처리가 가능해졌다.
(Ex_ 예를 들어 모든 컨트롤러의 요청이 들어오기 전 거쳐야 하는 사용자 인증, 로깅, 보안 등과 같은 공통적인 기능들을 모든 컨트롤러에서 구현을 해주었어야 했다는 뜻)

=> 조금 더 자세히 말해보자면, 클라이언트로부터 어떠한 요청이 오면 Tomcat(톰캣)과 같은 서블릿 컨테이너가 요청을 받게 됩니다. 그리고 이 모든 요청을 프론트 컨트롤러인 디스패처 서블릿이 가장 먼저 받게 됩니다. 그러면 디스패처 서블릿은 공통적인 작업을 먼저 처리한 후에 해당 요청을 처리해야 하는 컨트롤러를 찾아서 작업을 위임합니다.

서블릿 컨테이너?
: 웹 서버 안에서 동작하며, 웹 애플리케이션에 속하는 서블릿들의 생명주기를 관리하고, 클라이언트의 요청을 받아 적절한 서블릿에 전달하는 역할을 합니다.

즉, 서블릿 컨테이너는 서블릿의 생성부터 소멸까지 모든 과정을 관리하는 '프로그램의 실행 환경'이라고 볼 수 있습니다.

따라서, 서블릿 컨테이너는 서블릿의 실행 환경을 제공하고, 디스패처 서블릿은 그 환경 속에서 동작하면서 클라이언트의 요청을 적절한 컨트롤러에게 전달하는 역할을 합니다.


😊디스패처 서블릿의 흐름


[아래쪽 사진 기준]
❶클라이언트의 요청을 디스패처 서블릿이 받음
❷요청 정보를 통해 요청을 위임할 컨트롤러를 찾고 해당 정보 객체화
❸요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 2에서 반환된 객체 전달
❹핸들러 어댑터가 컨트롤러로 요청을 위임함
❺비지니스 로직을 처리함
❻컨트롤러가 반환값을 반환함
❼핸들러 어댑터가 반환값을 처리함
❽서버의 응답을 클라이언트로 반환함

"디스패처 서블릿을 통해 요청을 처리할 컨트롤러를 찾아서 위임하고, 그 결과를 받아오는구나" 정도로만 이해해도 괜찮지만, 자세한 과정이 궁금하니 더 조사해보았습니다.


1. 클라이언트의 요청을 디스패처 서블릿이 받음

앞서 설명하였듯, 디스패처 서블릿은 가장 먼저 요청을 받는 프론트 컨트롤러 입니다.
그렇기에 웹 컨텍스트에서 필터들을 지나 클라이언트의 요청을 가장 먼저 받게 됩니다.

참고로 InterCeptor가 컨트롤러에게 요청을 위임하는 것은 아니니, 그저 처리 순서에 따른 흐름도라고 생각하시면 됩니다.


2. 요청 정보를 통해 요청을 위임할 컨트롤러를 찾고 해당 정보 객체화

디스패처 서블릿이 요청을 받으면, 이 요청을 처리할 적절한 컨트롤러를 찾아야 합니다.

이를 위해 스프링은 HandlerMapping이라는 인터페이스를 제공하는데, 이 인터페이스의 역할은 요청 정보를 바탕으로 어떤 컨트롤러가 이 요청을 처리할 것인지를 찾아내는 것입니다.

요즘에는 주로 @Controller와 @RequestMapping 어노테이션을 이용해 컨트롤러를 만듭니다. 이런 방식의 컨트롤러를 찾아내는 것이 "RequestMappingHandlerMapping" 입니다. 이 객체는 @Controller로 작성된 모든 컨트롤러를 찾아내서, 그 안의 @RequestMapping어노테이션을 읽어서 어떤 URL과 HTTP 메소드에 대해 어떤 메소드가 호출되어야 하는지를 매핑해둡니다.

스프링 4.3 버전부터는 @RequestMapping의 축약형 어노테이션인 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping 등이 추가되어 HTTP 메소드에 따라 더 간결하게 사용할 수 있게 되었습니다.

예를 들어, @GetMapping("/example")은 @RequestMapping(value="/example", method=RequestMethod.GET)과 동일한 기능을 수행합니다.

이렇게 찾아낸 컨트롤러와 메소드 정보는 HandlerMethod라는 객체로 만들어져서 관리됩니다. 이 객체는 요청을 처리할 컨트롤러와 그 컨트롤러의 메소드 정보를 담고 있습니다.

요청이 들어오면, RequestMappingHandlerMapping은 요청 정보를 바탕으로 이전에 만들어 놓은 매핑 정보를 찾아서 적절한 HandlerMethod를 찾아냅니다. 그리고 이를 HandlerExecutionChain이라는 객체로 감싸서 반환하는데, 이 객체 안에는 요청을 처리할 HandlerMethod 외에도 이 요청을 처리하기 전후에 수행할 인터셉터들의 정보도 포함되어 있습니다.

이렇게 찾아낸 컨트롤러와 메소드 정보가 디스패처 서블릿에게 전달되면, 디스패처 서블릿은 이 정보를 바탕으로 적절한 컨트롤러의 메소드를 호출하여 요청을 처리하게 됩니다.


2-1. 🤯총정리

<즉, 정리해보자면>

스프링 프레임워크에서는 @Controller 어노테이션을 사용하여 클래스를 컨트롤러로 표시하고, @GetMapping, @PostMapping 등의 어노테이션을 사용하여 특정 HTTP 메소드를 처리하는 메소드를 지정합니다. 이렇게 표시된 컨트롤러 클래스와 메소드는 스프링 빈 컨테이너에 등록됩니다.

RequestMappingHandlerMapping은 스프링 빈 컨테이너를 통해 @Controller 어노테이션이 붙은 컨트롤러 클래스들을 찾아냅니다. 그리고 이 컨트롤러 클래스들의 메소드 중에서 @RequestMapping, @GetMapping, @PostMapping 등의 어노테이션이 붙은 메소드를 찾아내어, 이 메소드들이 어떤 HTTP 요청을 처리해야 하는지에 대한 정보를 매핑해둡니다. 이렇게 찾아낸 정보는 HandlerMethod 객체로 만들어져서 관리됩니다.

따라서, 클라이언트로부터 요청이 들어오면, RequestMappingHandlerMapping은 이전에 만들어 놓은 HandlerMethod 중에서 적절한 것을 찾아냅니다.

그 후 찾아낸 HandlerMethod와 해당 요청을 처리하기 전후에 실행해야 하는 인터셉터들을 HandlerExecutionChain 객체로 감싸서 디스패처 서블릿에게 반환합니다.


3. 요청을 컨트롤러로 위임할 핸들러 어댑터를 찾아서 2에서 반환된 객체 전달

이후에 컨트롤러로 요청을 위임해야 하는데, 디스패처 서블릿은 컨트롤러로 요청을 직접 위임하는 것이 아니라 HandlerAdapter를 통해  위임합니다.

그 이유는 스프링은 컨트롤러 구현 방식이 굉장히 다양하기 때문에, HandlerAdapter라는 어댑터 인터페이스를 통해 어댑터 패턴을 적용함으로써 컨트롤러의 구현 방식에 상관없이 요청을 위임할 수 있도록 하였습니다.


4. 핸들러 어댑터가 컨트롤러로 요청을 위임함

핸들러 어댑터(HandlerAdapter)가 하는 일은 크게 세 가지입니다.

  1. 인터셉터 실행
    : 컨트롤러가 요청을 처리하기 전후에 실행되어야 하는 인터셉터들을 실행합니다. 인터셉터는 보통 로깅, 인증, 인가 등 공통적인 작업을 처리하는 데 사용됩니다.
  2. 요청 매개변수 처리
    : HandlerAdapter는 @RequestParam, @RequestBody 등을 이용해 HTTP 요청의 일부를 메소드의 매개변수로 바인딩해주는 ArgumentResolver를 실행합니다. 예를 들어, 클라이언트가 보낸 JSON 데이터를 자바 객체로 변환하거나, URL의 쿼리 파라미터를 메소드의 매개변수로 변환하는 작업입니다.

    ArgumentResolver?

    웹 애플리케이션에서 클라이언트는 보통 HTTP 요청을 통해 서버에 정보를 전달하게 됩니다. 이때 HTTP 요청은 주로 URL의 쿼리 파라미터나 HTTP 본문(Body)에 정보를 담아 전달하게 됩니다.


    Ex1) URL의 쿼리 파라미터를 메소드의 매개변수로 변환

    클라이언트가 www.example.com/search?keyword=spring 같은 형태의 URL로 요청을 보내면, keyword=spring 부분이 URL의 쿼리 파라미터입니다. 이때 서버에서는 @RequestParam 어노테이션을 사용해 keyword라는 이름의 쿼리 파라미터를 메소드의 매개변수로 받을 수 있습니다.

    @GetMapping("/search")
    public String search(@RequestParam String keyword) {
    // ...
    }
    위와 같이 @RequestParam 어노테이션을 사용하면, 클라이언트가 keyword라는 이름으로 전달한 쿼리 파라미터의 값(spring)을 search 메소드의 매개변수 keyword로 받을 수 있게 됩니다.


    JSON 데이터를 자바 객체로 변환
    클라이언트가 { "name": "홍길동", "age": 20 } 같은 형태의 JSON 데이터를 HTTP 본문(Body)에 담아 POST 요청을 보낼 수 있습니다. 이때 서버에서는 @RequestBody 어노테이션을 사용해 JSON 데이터를 자바 객체로 변환해서 받을 수 있습니다.

    @PostMapping("/users")
    public String addUser(@RequestBody User user) {
    // ...
    }
    위와 같이 @RequestBody 어노테이션을 사용하면, 클라이언트가 HTTP 본문에 담아 보낸 JSON 데이터를 User 객체로 변환해서 addUser 메소드의 매개변수 user로 받을 수 있게 됩니다.


    이런 방식으로 스프링은 클라이언트가 HTTP 요청을 통해 보낸 데이터를 컨트롤러의 메소드 매개변수로 변환해주는 기능을 제공합니다. 이때 이 변환 작업을 실제로 수행하는 것이 ArgumentResolver입니다.

  3. 컨트롤러 메소드 실행
    : 위의 과정을 거쳐서 필요한 매개변수가 모두 준비되면, HandlerAdapter는 이 매개변수들을 메소드에 전달하고, 실제로 컨트롤러의 메소드를 실행합니다. 이때 Java의 리플렉션 기능을 사용해서 메소드를 실행합니다.

🤓🤓🤓🤓🤓🤓🤓🤓🤓🤓🤓🤓🤓🤓🤓🤓🤓

*위의 내용까지가 현재 단계에서 처리될 내용입니다.

  1. 응답 처리( ❼번의 내용 )
    : 컨트롤러 메소드가 실행된 후 반환된 결과를 클라이언트에게 보내줘야 하는데, 이때 ReturnValueHandler를 사용해서 응답 데이터를 처리합니다. 예를 들어, 컨트롤러 메소드가 반환한 객체를 JSON 형태로 변환하는 작업을 합니다.

이렇게 HandlerAdapter는 요청을 받아서 인터셉터를 실행하고, 요청 매개변수를 처리하고, 컨트롤러 메소드를 실행하고, 응답을 처리하는 일련의 과정을 관리합니다. 이를 통해 스프링은 다양한 방식으로 구현된 컨트롤러에 대해 일관된 요청 처리 과정을 제공할 수 있습니다.


5. 비즈니스 로직 처리

이후에 컨트롤러는 서비스를 호출하고 우리가 작성한 비지니스 로직들이 진행됩니다.


6. 컨트롤러가 반환값을 반환함

비지니스 로직이 처리된 후에는 컨트롤러가 반환값을 반환합니다. 응답 데이터를 사용하는 경우에는 주로 ResponseEntity를 반환하게 되고, 응답 페이지를 보여주는 경우라면 String으로 View의 이름을 반환할 수도 있습니다. 

컨트롤러가 반환하는 값에 따라 두 가지 경우?


  1. 응답 데이터를 반환하는 경우
    : 컨트롤러는 클라이언트에게 응답 데이터를 반환할 때 ResponseEntity를 사용할 수 있습니다. ResponseEntity는 응답 코드, 헤더, 본문 등을 포함하는 HTTP 응답 전체를 나타냅니다.

    따라서 ResponseEntity를 사용하면 컨트롤러에서 HTTP 응답의 모든 부분을 제어할 수 있습니다. 예를 들어, 다음과 같은 방식으로 ResponseEntity를 사용할 수 있습니다.

    @GetMapping("/users/{id}")
    public ResponseEntity getUser(@PathVariable Long id) {
    User user = userService.getUser(id);
    return ResponseEntity.ok(user);
    }

이 코드는 id에 해당하는 사용자를 찾아서 그 정보를 HTTP 응답 본문에 담아 반환합니다. ResponseEntity.ok(user)는 상태 코드를 200(OK)으로 설정하고, 사용자 정보를 HTTP 응답 본문에 담은 ResponseEntity를 생성합니다.


  1. 응답 페이지를 반환하는 경우
    : 컨트롤러는 클라이언트에게 보여줄 뷰의 이름을 문자열로 반환할 수 있습니다. 이 경우 스프링은 이 문자열을 뷰 이름으로 해석하고, 해당 이름의 뷰를 찾아 클라이언트에게 반환합니다. 예를 들어, 다음과 같은 방식으로 뷰 이름을 반환할 수 있습니다.

    @GetMapping("/hello")
    public String hello() {
    return "hello";
    }

이 코드는 "hello"라는 이름의 뷰를 클라이언트에게 보여주는 것을 의미합니다. 이 경우 스프링은 "hello"라는 이름의 뷰를 찾아서 클라이언트에게 반환하게 됩니다.


7. 핸들러 어댑터가 반환값을 처리함

HandlerAdapter는 컨트롤러로부터 받은 응답을 응답 처리기인 ReturnValueHandler가 후처리한 뒤, 디스패처 서블릿으로 돌려줍니다. 

  • 만약 컨트롤러가 ResponseEntity를 반환하면 HttpEntityMethodProcessor가 MessageConverter를 사용해 응답 객체를 직렬화하고 응답 상태(HttpStatus)를 설정합니다.

    • 예를 들어, 컨트롤러가 ResponseEntity.ok(user)를 반환하면, HttpEntityMethodProcessor는 user 객체를 JSON 형태로 직렬화하고, HTTP 응답 코드를 200(OK)으로 설정하게 됩니다.
  • 만약 컨트롤러가 View 이름을 반환하면 ViewResolver를 통해 View를 반환합니다.


8. 서버의 응답을 클라이언트로 반환함

디스패처 서블릿을 통해 반환되는 응답은 다시 필터들을 거쳐 클라이언트에게 반환됩니다.

이때 응답이 데이터라면 그대로 반환되지만, 응답이 화면이라면 View의 이름에 맞는 View를 찾아서 반환해주는 ViewResolver가 적절한 화면을 내려줍니다.


출처 :https://mangkyu.tistory.com/18

0개의 댓글