개발자 미니민의 개발스터디

[스프링 MVC 1편] MVC 프레임워크 만들기 (2)

by mini_min


Model 추가 - V3

이제, HttpServletRequest, HttpServletResponse 에서 벗어나 model 객체를 사용해보자!!!!!

request 객체를 model 로 사용하는 대신에 별도의 Model 객체를 만들어서 반환하자. 

우리가 구현한 컨트롤러들이 서블릿 기술을 전혀 사용하지 않도록 변경하면 된다. 

이렇게 구현하면 코드도 매우 단순해지고 테스트 코드 작성도 쉬워진다.

 

V3 버전의 구조

 

ModelView

지금까지는 컨트롤러에서 서블릿 종속적인 HttpServletRequest 를 사용했다. 그리고 Model 도 request.setAttribute 를 통해 데이터를 저장하고 뷰에 전달했다. 

이번에는 Model 을 직접 만들고 추가로 View 이름도 전달하는 객체를 만든다. 

 

package hello.servlet.web.frontcontroller;

import java.util.HashMap;
import java.util.Map;

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;
    }
}

 

뷰의 이름과 뷰를 렌더링 할 때 필요한 model 객체를 가지고 있다. 뷰에 필요한 데이터를 key, value 로 넣어주면 된다.

 

ControllerV3

package hello.servlet.web.frontcontroller.v3;

import hello.servlet.web.frontcontroller.ModelView;

import java.util.Map;

public interface ControllerV3 {

    //굉장히 단순! ! ! 서블릿에 종속적이지 않다, ~~HtppServletRequest 이런거 없음
    ModelView process(Map<String, String> paraMap);
}

 

컨트롤러는 서블릿 기술을 전혀 사용 하지 않는다!

HttpServletRequest 가 제공하는 파라미터는 프론트 컨트롤러가 paramMap 에 담아서 호출해주면 된다.

 

 

멤버 폼, 리스트, 등록 컨트롤러 ✨

package hello.servlet.web.frontcontroller.v3.controller;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;

import java.util.Map;

public class MemberFormControllerV3 implements ControllerV3 {
    @Override
    public ModelView process(Map<String, String> paraMap) {
        return new ModelView("new-form");
    }
}
package hello.servlet.web.frontcontroller.v3.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;

import java.util.List;
import java.util.Map;

public class MemberListControllerV3 implements ControllerV3 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public ModelView process(Map<String, String> paraMap) {
        List<Member> members = memberRepository.findAll();
        ModelView mv = new ModelView("members");
        mv.getModel().put("members", members);

        return mv;

    }
}
package hello.servlet.web.frontcontroller.v3.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;

import java.util.Map;

public class MemberSaveControllerV3 implements ControllerV3 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public ModelView process(Map<String, String> paraMap) {
        String username = paraMap.get("username");
        int age = Integer.parseInt(paraMap.get("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        ModelView mv = new ModelView("save-result");
        mv.getModel().put("member", member);
        return mv;
    }
}

 

파라미터 정보는 map 에 담겨 있다. map 에 필요한 요청 파라미터를 조회하면 된다. 

 

FrontControllerV3

package hello.servlet.web.frontcontroller.v3;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v2.ControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberFormControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberListControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberSaveControllerV2;
import hello.servlet.web.frontcontroller.v3.controller.MemberFormControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberListControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberSaveControllerV3;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;


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

    //url 을 넣고 ControllerV1 을 호출해~ 이런식으로 짤거임.
    private Map<String, ControllerV3> controllerV1Map = new HashMap<>();

    //미리 저장해둠
    public FrontControllerServletV3() {
        controllerV1Map.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
        controllerV1Map.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
        controllerV1Map.put("/front-controller/v3/members", new MemberListControllerV3());

    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FrontControllerServletV3.service");

        String requestURI = request.getRequestURI();

        ControllerV3 controller = controllerV1Map.get(requestURI);

        if ( controller == null)
        {
            //못찾았을 때 404 리턴하기
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

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

        String viewName = mv.getViewName();//논리이름 new-form
        //new-form
        MyView view = viewResolver(viewName);
        view.render(mv.getModel(),request,response);

    }

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

    private Map<String, String> createParaMap(HttpServletRequest request) {
        //paramMap
        Map<String, String[]> parameterMap =
                request.getParameterMap();

        Map<String, String> m = new HashMap<>();

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

        parameterMap.forEach((key, value)
                -> System.out.println("key: " + key + ", value: " + request.getParameter(key)));

        parameterMap.forEach((key, value)
                -> m.put(key, request.getParameter(key)) );

        return m;

    }
}

 

✨ creatParamMap() : HttpServletRequest 에서 파라미터 정보를 꺼내서 Map 으로 변환한다. 그리고 해당 Map 을 컨트롤러에 전달하면서 호출한다. 

 

✨ viewResolver : 컨트롤러가 반환한 논리 뷰의 이름을 실제 물리 뷰로 변경한다. 그리고 실제 물리 경로에 있는 MyView 객체를 반환한다!

논리 뷰 이름 : members

물리 뷰 이름 : /WEB-INF/views/members.jsp

 

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

뷰 객체를 통해 HTML 화면을 렌더링해준다. 

이때, 뷰 객체의 render() 는 모델 정보도 함께 받는다. 

 

MyView

package hello.servlet.web.frontcontroller;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;

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);
    }

    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));
    }


}

 

 


단순하고 실용적인 컨트롤러 - V4

앞서 만든 V3 은 서블릿 종속성을 제거하고 뷰 경로의 중복도 제거하여 만든 컨트롤러이다. 

이번에는 컨트롤러에서 modelView 를 반환하지 않고 viewname 만 반환하도록 개발해본다. 

 

 

 

ControllerV4

package hello.servlet.web.frontcontroller.v4;

import hello.servlet.web.frontcontroller.ModelView;

import java.util.Map;

public interface ControllerV4 {

    String process(Map<String, String> paramMap, Map<String, Object> model);

}

 

v4 의 인터페이스에는 ModelView 가 없다. model 객체는 파라미터로 전달되기 때문에 그냥 사용하면 되고, 결과로 뷰의 이름만 반환해주면 된다.

 

멤버 폼, 리스트, 등록 컨트롤러 ✨

package hello.servlet.web.frontcontroller.v4.controller;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import hello.servlet.web.frontcontroller.v4.ControllerV4;

import java.util.Map;

public class MemberFormControllerV4 implements ControllerV4 {

    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        return "new-form";
    }
}
package hello.servlet.web.frontcontroller.v4.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import hello.servlet.web.frontcontroller.v4.ControllerV4;

import java.util.List;
import java.util.Map;

public class MemberListControllerV4 implements ControllerV4 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        List<Member> members = memberRepository.findAll();

        model.put("members", members);
        return "members";
    }
}
package hello.servlet.web.frontcontroller.v4.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v4.ControllerV4;

import java.util.Map;

public class MemberSaveControllerV4 implements ControllerV4 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        String username = paramMap.get("username");
        int age = Integer.parseInt(paramMap.get("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        model.put("member", member);
        return "save-result";

    }

}

 

FrontControllerV4

package hello.servlet.web.frontcontroller.v4.controller;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v4.ControllerV4;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {

    //url 을 넣고 ControllerV1 을 호출해~ 이런식으로 짤거임.
    private Map<String, ControllerV4> controllerV1Map = new HashMap<>();

    //미리 저장해둠
    public FrontControllerServletV4() {
        controllerV1Map.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
        controllerV1Map.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
        controllerV1Map.put("/front-controller/v4/members", new MemberListControllerV4());

    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FrontControllerServletV4.service");

        String requestURI = request.getRequestURI();

        ControllerV4 controller = controllerV1Map.get(requestURI);

        if ( controller == null)
        {
            //못찾았을 때 404 리턴하기
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Map<String, String> paramMap = createParaMap(request);
        Map<String, Object> model = new HashMap<>();
        String viewName = controller.process(paramMap, model);
        //new-form
        MyView view = viewResolver(viewName);
        view.render(model, request, response);

    }

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

    private Map<String, String> createParaMap(HttpServletRequest request) {
        //paramMap
        Map<String, String[]> parameterMap =
                request.getParameterMap();

        Map<String, String> m = new HashMap<>();

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

        parameterMap.forEach((key, value)
                -> System.out.println("key: " + key + ", value: " + request.getParameter(key)));

        parameterMap.forEach((key, value)
                -> m.put(key, request.getParameter(key)) );

        return m;

    }
}

 

프론트 컨트롤러는 이전과 거의 동일하다. 

 

        Map<String, String> paramMap = createParaMap(request);
        Map<String, Object> model = new HashMap<>();
        String viewName = controller.process(paramMap, model);
        //new-form
        MyView view = viewResolver(viewName);
        view.render(model, request, response);

 

모델 객체를 프론트 컨트롤러에서 생성해서 넘겨준다. 컨트롤러에서 모델 객체에 값을 담으면 해당 객체에 남게 된다.

컨트롤러가 직접 뷰의 논리 이름을 반환하므로 이 값을 이용해 실제 물리 뷰를 찾을 수 있다. 

 

 

 

블로그의 정보

개발자 미니민의 개발로그

mini_min

활동하기