Spring/Spring MVC

인프런 김영한 스프링 MVC 프레임워크 만들기 - 나만의 FrontController 만들기

제우제우 2024. 4. 16. 17:20

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 | 김영한 - 인프런

김영한 | 웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습

www.inflearn.com

 

오늘 해볼 내용은 스프링 MVC 1편에서 섹션 4 MVC 프레임워크 만들기 유연한 컨트롤러2 - V5를 복습할 것이다.

복습 방법은 FrontControllerV6를 만들어서 기존 ControllerV3, ControllerV4가 동작되게 만들겠다.

 

커스텀 내용 

MVC 프레임워크 만들기 강의를 들어보면 기존 버전에서 개발자들이 편하게 개발하기 위해서 
HttpServletRequest, HttpServletResponse 객체를 컨트롤러에 보내지 않는 방법으로 리팩토링한 적이 있다.

어댑터에 HttpServeletRequest, HttpServletResponse를 보내지 않고 클라언트가 보낸 파라미터 값과 

해당 컨트롤러 객체만 어댑터에 보내서 요청을 처리하도록 리팩토링 해보겠다.

 

V6(Custom) 구조

 

기존 v5와 구조는 똑같다!

 

1. index.html(welcome page에 내가 만든 url 추가)

 <li>FrontController - v6(custom) - v3
        <ul>
            <li><a href="/front-controller/v6/v3/members/new-form">회원가입</a></li>
            <li><a href="/front-controller/v6/v3/members">회원목록</a></li>
        </ul>
    </li>
 <li>FrontController - v6(custom) - v4
        <ul>
            <li><a href="/front-controller/v6/v4/members/new-form">회원가입</a></li>
            <li><a href="/front-controller/v6/v4/members">회원목록</a></li>
        </ul>
  </li>

 

2. AdapterHandler 인터페이스 - CustomHandlerApdater

public interface CustomHandlerAdapter {
    boolean check(Object handler);
    ModelView handle(HashMap<String, String> paramMap, Object handler);
}

 

- handle 메서드의 파라미터가 HttpServeltRequest,  HttpServeltResponse, handler 에서 paramMap, handler로 교체

 

3. CustomHandlerApdater를 구현한 ControllerV3를 다루기 위한 어댑터 - ControllerAdapterV3

public class ControllerV3Adapter implements CustomHandlerAdapter {
    @Override
    public boolean check(Object handler) {
       return (handler instanceof ControllerV3);
    }

    @Override
    public ModelView handle(HashMap<String, String> paramMap, Object handler) {
        ControllerV3 controller = (ControllerV3) handler;
        return controller.process(paramMap);
    }
}

 

4. CustomHandlerApdater를 구현한 ControllerV4를 다루기 위한 어댑터 - ControllerAdapterV4

public class ControllerV4Adapter implements CustomHandlerAdapter {
    @Override
    public boolean check(Object handler) {
        return (handler instanceof ControllerV4);
    }
    @Override
    public ModelView handle(HashMap<String, String> paramMap, Object handler) {
        System.out.println(paramMap);
        ControllerV4 controller = (ControllerV4) handler;
        Map<String, Object> model = new HashMap<>();
        ModelView modelView = new ModelView(controller.process(paramMap, model));
        modelView.setModel(model);
        return modelView;
    }
}

 

5. FrontController -  FrontControllerServeltV6 

@WebServlet(name = "frontControllerServletV6", urlPatterns = "/front-controller/v6/*")
public class FrontControllerServletV6 extends HttpServlet {
    private final HashMap<String, Object> handlerMap = new HashMap<>();
    private final List<CustomHandlerAdapter> adapters = new ArrayList<>();

    public FrontControllerServletV6() {
        initCreateHandlerMap();
        initCreateAdapters();
    }
    private void initCreateAdapters() {
        adapters.add(new ControllerV3Adapter());
        adapters.add(new ControllerV4Adapter());
    }
    private void initCreateHandlerMap() {
        // v3
        handlerMap.put("/front-controller/v6/v3/members/new-form", new MemberFormControllerV3());
        handlerMap.put("/front-controller/v6/v3/members/save", new MemberSaveControllerV3());
        handlerMap.put("/front-controller/v6/v3/members", new MemberListControllerV3());
        // v4
        handlerMap.put("/front-controller/v6/v4/members/new-form", new MemberFormControllerV4());
        handlerMap.put("/front-controller/v6/v4/members/save", new MemberSaveControllerV4());
        handlerMap.put("/front-controller/v6/v4/members", new MemberListControllerV4());
    }
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        Object controller = handlerMap.get(requestURI);
        if(controller == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        CustomHandlerAdapter adapter = getHandlerAdapter(controller);

        HashMap<String, String> paramMap = new HashMap<>();
        createParamMap(request, paramMap);
        System.out.println(paramMap);
        ModelView modelView = adapter.handle(paramMap, controller);
        MyView myView = new MyView(viewResolver(modelView.getViewName()));
        myView.render(modelView.getModel(), request, response);
    }

    private CustomHandlerAdapter getHandlerAdapter(Object controller) {
        for (CustomHandlerAdapter target : adapters) {
            if(target.check(controller)){
                return target;
            }
        }
        throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler =" + controller);
    }
    private static void createParamMap(HttpServletRequest request, HashMap<String, String> paramMap) {
        request.getParameterNames().asIterator()
                .forEachRemaining(paramKey -> paramMap.put(paramKey, request.getParameter(paramKey)));
    }
    private String viewResolver(String viewName) {
        return "/WEB-INF/views/" + viewName + ".jsp";
    }

}

 

동작 테스트 

메인 페이지

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

회원가입 

 

회원가입 결과

 

회원 목록

성공!!!

 

전체적인 동작 설명

1. 클라이언트가 웹 애플리케이션 서버로 요청을 한다.

2. 서블릿 컨테이너는 해당 url에 대한 서블릿(frontController)이 있으면 해당 서블릿을 반환하고 없으면 서블릿을 생성한다. (이때 이 서블릿은 싱글톤이다.) 

---- 서블릿 service() 메서드 동작 -----

3. 해당 서블릿은 들어온 url에 맞게 컨트롤러 객체를 반환한다. -> 없으면 404에러 

4. 객체에 맞는 어댑터를 반환한다. -> 없으면 IllegalArgumentException

5. 클라이언트가 보낸 데이터를 Map에 저장한다. (paramMap)

--- 어댑터 동작 ---

6. 어댑터에 핸들러와(controller) , paramMap을 매개변수로 보낸다. (handle 메서드)

7. 해당 어댑터는 이제 매개변수로 받은 핸들러에 요청을 보낸다.(process) 각 어댑터마다 동작이 다르다.

8. 어댑터는 핸들러가 반환한 값으로 모델뷰를 생성하고 반환한다.

--- 클라이언트에게 응답 과정 ---

9. 받은 ModelView를 통해서 랜더링을 한다. 

9 - 1  viewPath 만들기 viewResolver() 메서드를 통해 viewPath를 완성한다. 

9 - 2. 완성한 viewPath를 통해 MyView객체를 만든다. 

9 - 3. MyView rend() 메서드 호출 

9 - 4. HttpServeletRequest 객체에 모델 데이터를 request.setAttribute (해당 view에서 이 데이터를 가지고 HTML 만든다)

9 - 5. RequestDispacther를 통해서 forward한다. 

forward?

서블릿 컨테이너에서 요청 처리를 다른 서블릿이나 JSP로 전달하는 데 서용한다.

새로운 서블릿이나 JSP에서 요청을 처리하고, 결과를 응답 객체에 기록하고 서블릿 컨테이너는 응답을 클라이언트에게 반환한다. 

 

더 상세한 JSP 요청 처리 과정 

  1. 클라이언트 요청 수신: 먼저, 클라이언트(웹 브라우저 등)로부터 HTTP 요청이 JSP 페이지에 도착합니다. 이 요청은 웹 애플리케이션 서버(예: Apache Tomcat)로 전송됩니다.
  2. JSP 컴파일: 서버는 요청된 JSP 파일을 컴파일하여 서블릿 클래스로 변환합니다. 이 서블릿 클래스는 Java 코드로 작성된 동적 컨텐츠를 생성하는 데 사용됩니다. (JSP 페이지 동적 컨텐츠 일부 생성)
  3. 서블릿 실행: 컴파일된 JSP 페이지는 서블릿으로 실행됩니다. 이때, service() 메서드가 호출되고, 요청 및 응답 객체가 이 메서드에 전달됩니다.
  4. 동적 컨텐츠 생성: JSP 페이지는 Java 코드와 HTML 또는 기타 마크업 언어로 구성됩니다. 서블릿은 이러한 코드를 실행하고, 동적으로 생성된 컨텐츠를 생성하여 응답 객체에 씁니다. (JSP 페이지 forward를 통한 최종적인 동적 컨텐츠 완성)
  5. 응답 전송: 서블릿이 생성한 응답은 HTTP 응답으로 변환되고 클라이언트에게 전송됩니다. 이 응답에는 HTML, CSS, JavaScript 및 기타 리소스가 포함될 수 있습니다.

이러한 요청 처리 과정을 통해 JSP는 동적으로 생성된 웹 페이지를 제공하고, 서버 측 로직과 사용자 인터페이스를 통합하여 웹 애플리케이션을 구축하는 데 사용됩니다.

 

 

 

'Spring > Spring MVC' 카테고리의 다른 글

MockMvc  (0) 2024.08.19