ecsimsw

DispatcherServlet이 요청을 처리하고 응답하는 과정 본문

DispatcherServlet이 요청을 처리하고 응답하는 과정

JinHwan Kim 2021. 4. 16. 01:15

HandlerMapping과 HandlerAdapter

(@포모) 같이 공부하는 친구가 HandlerMapping과 HandlerAdapter의 차이를 질문해주었다. 내가 모호하게 알고 있었다는 것을 펜을 들고 설명하는 순간 알게 되었다. 

 

혼자 머릿속에서 정리하는 것과 말로 설명할 수 있는 것은 정말 차이가 큰 것 같다. 질문을 받는 것은 정말 감사한 일이다.

 

아 그리고 부끄럽지만 영어 철자를 좀 제대로 봐야겠다. 안다고 생각했던 단어였는데 칠판에 쓰고 설명하려니 철자가 버벅인 경우도 많았다. 웃어 넘겼지만 평소에 조금 더 신경써서 봐야겠다. 

 

HandlerMapping

FrontController 패턴으로 모든 요청을 DispatcherServlet이 처리한다. DispatcherServlet은 사용자의 요청을 처리할 핸들러를 직접 검색하지 않고 HandlerMapping 객체에 처리를 위임한다. 요청이 들어오면 그 경로를 처리하는 컨트롤러를 검색하고 다시 DispatcherServlet에 반환하는 것이다.

 

근데 왜 '핸들러'와 '컨트롤러'를 섞어 사용하는 것일까. 그래서 HandlerMapping이 반환하는 것이 Handler일까 Controller일까? 요청을 처리하는 객체가 꼭 컨트롤러일 필요가 없기 때문이다. 어렵다면 그냥 SpringMVC에서 Controller는 Handler의 일부라고 생각하면 될 것 같다. 

 

만약 handlerMapping이 처리할 핸들러를 못 찾으면 어떻게 될까? 원래는 톰캣이 Default Servlet이 자원 반환 등의 처리를 하지만, 스프링에서는 Dispatcher servlet이 모든 요청을 처리하기 때문에 따로 설정해줘야 한다. 

 

HandlerAdapter

HandlerMapping으로 DispatcherServlet에 요청 처리를 담당할 컨트롤러 객체가 들어왔다. HandlerAdapter는 무슨 일을 할까

@Controller
@RequestMapping("/game")
public class GameController {
    private final GameService gameService;

    public GameController(final GameService gameService) {
        this.gameService = gameService;
    }

    @GetMapping("/create/{roomId}")
    private String createGame(@PathVariable final Long roomId) {
        gameService.create(roomId);
        return "redirect:/game/load/" + roomId;
    }
}

 

"/game/create/1"이 요청으로 들어오면 GameController의 createGame()가 수행된다. 이상한 흐름이지만 너무 편하게 그냥 외워버렸던 것 같다. 아래는  SparkJava를 이용해서 마찬가지로 GameController를 구현한 코드이다. 뭐가 이상하다는 걸까?

 

public class GameController {
    private final GameService gameService;

    public GameController(Connection connection) {
        this.gameService = new GameService(connection);
    }

    public void mapping() {
        createGame();
    }

    private void createGame() {
        get("/game/create/:roomId", (req, res) -> {
            final Long roomId = RequestHandler.roomId(req);
            gameService.create(roomId);
            res.redirect("/game/load/" + roomId);
            return null;
        });
    }
}

 

정답은 스프링의 GameController에선 createGame을 호출하는 로직이 전혀 없다는 것이다. 그리고 그것이 바로 HandlerAdapter의 역할이다. 

 

HandlerAdapter는 DispatcherServlet에서 넘어온 컨트롤러에서 요청에 해당하는 메서드를 호출한다. 그리고 그 결과를 ModelAndView 객체로 DispatcherServlet에게 리턴해준다.

 

ViewResolver와 View

ViewResolver는 컨트롤러에서 넘긴 처리 결과(ModelAndView)를 읽어 View 객체를 리턴한다. 다시 DispatcherServlet은 리턴받은 View에게 응답 결과 생성을 요청하고 클라이언트에 응답한다.

 

ServletWebApplicationContext

위 설명에서 빠진게 있다. View와 ModelAndView는 각각 ViewResolver와 HandlerAdapter가 생성한다고 하는데, 그렇다면 HandlerMapping, HandlerAdapter, ViewResolver는 어디서 오는걸까? 개발자가 매번 생성해서 사용해야하는 걸까?

 

정답은 스프링 컨테이너에 빈 객체로 등록해두었다가 사용되는 것이다. DispatcherServlet은 설정 파일을 읽어 RootWebApplicationContext를 상속한 ServlerWebApplicationContext를 생성하고 그 안에 HandlerMapping, HandlerAdapter, ViewResolver를 빈으로 등록해두는 것이다. 즉 개발자는 설정 파일에 이들에 대한 설정 정보만 적어두고, 요청을 처리할 Handler와 View가 반환할 응답 파일만 만들어주기만 하면 된다.

 

 

 

Comments