직접 만든 미니 MVC framework

최준호·2021년 7월 6일
0

Spring

목록 보기
15/47

Spring MVC와 내가 만든 미니 MVC

지금까지 만든 MVC 프레임워크와 실제 Spring 프레임워크를 비교해보자.

지금까지의 구조를 잘 정리하고 이해했다면 우리가 만든 구조와 다른점이 거의 없다는 것을 알 수 있다. 위 그림과 아래 그림의 차이는 FrontController라는 이름 대신 DispatcherServlet이란 이름 차이일뿐 둘다 Servlet을 상속받아서 사용되는것도 똑같다. 그럼 실제 코드로 어떻게 동작하는 구조인지도 살펴보자

Spring MVC 실제 동작 구조

  1. Dispatcher Servlet 동작 구조

    • Spring MVC도 프론트 컨트롤러 패턴으로 구현되어 있다. Spring의 프론트 컨트롤러가 바로 DispatcherServlet이다. 그리고 이 DispatcherServlet의 부모 클래스도 HttpServlet을 상속받아서 사용되고 동작된다. 자세한 구조는 실제로 코드를 확인해보길 바란다. 실제 Spring boot는 DispatcherServlet을 서블릿으로 자동 등록하면서 모든 경로("/*")에 대해 매핑하고 있다.

    • 요청 흐름

      1. 서블릿이 호출되면 HttpServlet이 제공하는 service()가 호출된다.

      2. service()를 시작으로 여러 메서드들이 호출되는데 그 중 doDispatcher()가 호출된다. 그리고 이 doDispatcher()가 핵심코드이며 이 코드를 분석해보자.

        protected void doDispatch(HttpServletRequest request, HttpServletResponse 
        response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        ModelAndView mv = null;
        // 1. 핸들러 조회
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {noHandlerFound(processedRequest, response);
        return;
        }
        // 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        // 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        processDispatchResult(processedRequest, response, mappedHandler, mv,
        dispatchException);
        }

        우리가 이전에 만들었던 MVC 프레임워크에서 핸들러를 조회하고 반환받는 getHandler()가 보일것이다 이 핸들러를 통해 컨트롤러의 정보를 가져오고 핸들러에서 가져온 컨트롤러 정보로 adapter를 조회하여 어댑터를 가져오고 반환 받은 어댑터를 통해 handle()을 실행하여 ModelAndView를 반환 받아서 해당 객체로 process를 진행한다.

        private void processDispatchResult(HttpServletRequest request,
        HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView 
        mv, Exception exception) throws Exception {
        // 뷰 렌더링 호출
        render(mv, request, response);
        }
        protected void render(ModelAndView mv, HttpServletRequest request,
        HttpServletResponse response) throws Exception {
        View view;
        String viewName = mv.getViewName();
        // 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        // 8. 뷰 렌더링
        view.render(mv.getModelInternal(), request, response);
        }

        그리고 해당 코드도 우리가 익숙한 코드이다. processDispatchResult()는 반환 받은 ModelAndView를 통해 render()를 실행하고 render()는 뷰 리졸버를 통해서 View 객체를 찾고 View를 반환 받아 해당 view를 실제 view로 전달하는 역할까지 수행한다.

      3. 코드를 분석해보면 Spring MVC의 큰 강점은 DispatcherServlet의 코드 변경 없이 원하는 기능을 확장할 수 있다는 점이다. 지금까지 설명한 대부분 기능을 인터페이스로 제공하고 있어 해당 인터페이스를 상속하여 확장하여 사용이 가능하다.

  2. 핸들러 매핑과 핸들러 어댑터

    현재 @Controller로 많이 사용하고 있지만 과거에는 @Controller대신 인터페이스의 Controller를 사용했다. 해당 방법으로 어떻게 동작했었는지 확인해보자.

    org.springframework.web.servlet.mvc.Controller

    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.Controller;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @Component("/springmvc/old-controller")
    public class OldController implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request,
        HttpServletResponse response) throws Exception {
            System.out.println("OldController.handleRequest");
            return null;
        }
    }

    해당 코드와 같이 Controller를 구현했었야했는데. 동작하는 구조는

    1. 핸들러 매핑으로 핸들러 조회
      1. HandlerMapping을 순서대로 실행해서 핸들러를 찾는다.
      2. 빈 이름으로 핸들러를 조회하여 BeanNameUrlHandlerMapping이 실행에 성공하고 핸들러인 OldController를 반환한다.
    2. 핸들러 어댑터 조회
      1. HandlerAdapter의 supports()를 호출하고 true가 반환
      2. SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원하므로 대상이 된다.
    3. 핸들러 어댑터 실행
      1. 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter를 실행하면서 핸들러 정보도 함께 넘겨준다.
      2. SimplecontrollerHandlerDatapter는 핸들러인 OldController를 내부에서 실행하고 그 결과를 반환한다.
  3. 뷰 리졸버

    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.mvc.Controller;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    @Component("/springmvc/old-controller")
    public class OldController implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request,
        HttpServletResponse response) throws Exception {
            System.out.println("OldController.handleRequest");
            return new ModelAndView("new-form");
        }
    }

    위의 OldController에서 null을 반환하던 곳을 ModelAndView를 실제로 반환하도록 수정하여 뷰 정보를 넣어보자.

    우리는 자원의 view 이름을 입력해줬지만 웹 브라우저에는 아직 WhiteLable Error Page가 나올것이다. 이는 prefix와 suffix 값이 입력되지 않아서 그런것인데. 실제 new-form이란 view의 자원은 /WEB-INF/views/new-form.jsp 파일이기 때문이다. 그리고 이 정보는 java로 입력이 가능한데 InternalResourceViewResolver 스프링 컨테이너의 빈을 등록하는 방법으로 등록하는 방법도 있고 그 방법은 인터넷을 찾아보기 바란다! 우리는 간단하게 application.properties 파일에 추가로 등록해주려 한다.

    spring.mvc.view.prefix=/WEB-INF/views/
    spring.mvc.view.suffix=.jsp

    위 코드를 application.properties에 추가로 입력해주면 이제 정상적으로 페이지를 찾는 것을 확인할 수 있다. 이 방법으로 통해 반환하는 new ModelAndView("/WEB-INF/views/new-form.jsp")로 반환해도 실제 동작하긴 하는데 공통으로 처리해야할 부분을 각각 처리하게 되므로 권장하는 방법은 아니다.

    뷰 리졸버의 동작 구조는

    1. 핸들러 어댑터 호출
      1. 핸들러 어댑터를 통해 new-form이라는 뷰 이름을 획득한다.
    2. ViewResolver 호출
      1. new-form이란 뷰 이름으로 viewResolver 메서드들을 호출한다.
      2. BeanNameViewResolver는 new-form이라는 스프링 빈으로 등록된 뷰를 찾아야하는데 없다.
      3. InternalResourceViewResolver가 호출된다.
    3. InternalResourceViewResolver
      1. 뷰 리졸버에서 InternalResourceView를 반환한다.
    4. InternalResourceView
      1. InternalResourceView는 JSP처럼 forward()를 호출해서 처리할 수 있는 경우에 사용한다.
    5. view.render()
      render()가 호출되고 InternalResourceView는 forward()를 호출하여 JSP를 실행한다.
    • 만약 Thymeleaf 뷰 템플릿을 사용하면 ThymeleafResolver를 등록해서 사용해야한다. 이처럼 뷰 템플릿에 따라 각각의 뷰 리졸버를 사용하여 render하는 과정을 거치면 뷰로 정보가 전달되어진다.

출처 https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

profile
코딩을 깔끔하게 하고 싶어하는 초보 개발자 (편하게 글을 쓰기위해 반말체를 사용하고 있습니다! 양해 부탁드려요!) 현재 KakaoVX 근무중입니다!

0개의 댓글