3 분 소요


Model 추가 - V3

서블릿 종속성 제거
Controller 입장에서 요청 파라미터 정보를 자바의 Map으로 대신 넘기도록 하면 서블릿 기술(HttpServletRequest, HttpServletResponse)를 몰라도 동작할 수 있다.
그리고 request 객체를 Model로 사용하는 대신에 별도의 Model 객체를 만들어서 반환하면 된다.

View 이름 중복 제거
Controller는 View의 논리 이름을 반환하고, 실제 물리 위치의 이름은 FrontController에서 처리하도록 한다.

  • /WEB-INF/views/new-form.jsp -> new-form
  • /WEB-INF/views/save-result.jsp -> save-result
  • /WEB-INF/views/members.jsp -> members

V3 구조
v3

ModelView
V1, V2는 Controller에서 서블릿에 종속적인 HttpServletRequest를 사용했고, Model도 request.setAttribute()를 통해 데이터를 저장하고 뷰에 전달했다.
서블릿의 종속성을 제거하기 위해 Model을 직접 만들었다.

ModelView

public class ModelView {
    private String viewName;
    private Map<String, Object> model = new HashMap<>();

    public ModelView(String viewName) {
        this.viewName = viewName;
    }

    public String getViewName() {
        return viewName;
    }

    public void setViewName(String viewName) {
        this.viewName = viewName;
    }
    
    public Map<String, Object> getModel() {
        return model;
    }

    public void setModel(Map<String, Object> model) {
        this.model = model;
    }
}

View의 이름과 View를 렌더링할때 필요한 model 객체를 가지고 있다. model은 단순히 map으로 되어 있으므로 Controller애서 View에 필요한 데이터를 key, value로 넣어주면 된다.

ControllerV3

public interface ControllerV3 {
    ModelView process(Map<String, String> paramMap);
}

HttpServletRequest가 제공하는 파라미터는 FrontController가 paramMap에 담아서 호출해주면 된다. 응답 결과로 View 이름과 View에 전달할 Model 데이터를 포함하는 ModelView 객체를 반환하면 된다.

MemberFormControllerV3

@Override
public ModelView process(Map<String, String> paramMap) {
    return new ModelView("new-form");
}

ModelView를 생성할 때 new-form이라는 view의 논리적인 이름을 지정해준다.

MemberSaveControllerV3

@Override
public ModelView process(Map<String, String> paramMap) {
    String username = paramMap.get("username");
    int age = Integer.parseInt(paramMap.get("age"));
    
    Member member = new Member(username, age);
    memberRepository.save(member);
    
    ModelView mv = new ModelView("save-result");
    mv.getModel().put("member", member);
    return mv;
}

paramMap.get("username");
파라미터 정보는 map에 담겨있기 때문에 map에서 필요한 요청 파라미터를 조회하면 된다.

mv.getModel().put("member", member);
model은 단순한 map이므로 ModelView에서 필요한 member객체를 담고 반환한다.

FrontControllerServletV3

@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {

    private Map<String, ControllerV3> controllerMap = new HashMap<>();

    public FrontControllerServletV3() {
        controllerMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
        controllerMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
    }

    // --- void service()
    String requestURI = request.getRequestURI();

    ControllerV3 controller = controllerMap.get(requestURI);
    if (controller == null) {
        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    Map<String, String> paramMap = createParamMap(request);
    ModelView mv = controller.process(paramMap);

    String viewName = mv.getViewName();
    MyView view = viewResolver(viewName);
    view.render(mv.getModel(), request, response);

    // --- Method

    private Map<String, String> createParamMap(HttpServletRequest request) {
        Map<String, String> paramMap = new HashMap<>();

        request.getParameterNames().asIterator()
        .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
        return paramMap;
    }

    private MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }
}

createParamMap(request)
HttpServletRequest에서 파라미터 정보를 꺼내서 Map으로 변환한다. 그리고 paramMap을 Controller에 전달하면서 호출한다.

viewResolver MyView view = viewResolver(viewName)
Controller가 반환한 논리 View 이름을 실제 물리 View 경로로 변경한다. 그리고 실제 물리 경로가 있는 MyView 객체를 반환한다.

  • 논리 View 이름: new-form
  • 물리 View 경로: /WEB-INF/views/new-form.jsp

view.render(mv.getModel(), request, response)

  • View 객체를 통해서 HTML 화면을 렌더링 한다.
  • View 객체의 render()는 model 정보도 함께 받는다.
  • JSP는 request.getAttribute()로 데이터를 조회하기 때문에, model의 데이터를 꺼내서 request.setAttribute()로 담아둔다.
  • JSP로 forward해서 JSP를 랜더링 한다.

MyView

public class MyView {

    private String viewPath;

    public MyView(String viewPath) {
        this.viewPath = viewPath;
    }

    public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }

    // --- V3 추가된 코드
    public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        modelToRequestAttribute(model, request);
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
    
    private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
        model.forEach((key, value) -> request.setAttribute(key, value));
    }
}


정리

  • 서블릿의 종속성을 제거하기 위한 Model을 만들었고, 추가로 View 이름까지 전달하는 객체를 만들었다.(ModelView)
  • model은 단순히 map으로 되어있기 때문에 필요한 데이터(ex. username, age)를 key,value로 넣어주면 된다.
  • 인터페이스 또한 return type을 ModelView로 지정하였고, 입력 매개변수에도 Map이 들어가게 했다.
  • [FrontController] createParamMap()메서드를 이용해 request 파라미터에서 정보를 꺼내서 map 으로 변환했다.
  • [FrontController] map으로 변환한 정보를 paramMap에 담아 Controller를 호출할때 값을 전달해 주었다.
  • [Controller] Controller는 paramMap.get()메서드를 사용하여 값을 사용할 수 있다.
  • [Controller] 논리 View 이름(ex.”new-form”) & member객체(username, age)를 Map 타입의 model에 집어 넣어 ModelView 타입으로 리턴해준다.
  • [FrontController] viewResolver()를 활용하여 넘어온 논리 View 이름을 물리 View 경로로 만들어준다.
  • [FrontController] render안에 model 매개변수를 추가하여 MyView 호출
  • [MyView] JSP는 request.getAttribute()로 데이터를 조회하기 때문에 modelToRequestAttribute()를 통해서 model에 있는 데이터를 꺼내서 request.setAttribute()로 담는다.
  • [MyView] 마지막으로 전달받은 물리 View 경로(viewPath)와 HttpServletRequest에 담아둔 데이터로 JSP로 렌더링 한다.


<출처 : 인프런 - 스프링 MVC 1편 : 백엔드 웹 개발 핵심 기술(김영한)>

댓글남기기