일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 필기
- jsp
- git
- sqldeveloper
- LeetCode
- 스터디
- 알고리즘
- 뇌정리
- Real MySQL
- 함수형 코딩
- 주간회고
- 미니프로젝트
- 2020년 제4회 정보처리기사 필기 문제 분석
- 회고
- Jackson
- 서평
- post
- hackerrank
- Python
- 성적프로그램
- 항해99
- 코드숨
- 2020년 정보처리기사 4회
- 책리뷰
- 정보처리기사
- java
- algorithms
- 2020년 일정
- If
- Til
- Today
- Total
조컴퓨터
#MVC 프레임워크 만들기 - v1, v2, v3, v4 본문
###뇌 정리용 글###
FrontController 패턴 도입 (=DispatcherServlet) - v1
내부 로직은 기본 서블릿과 거의 같다. 아래 그림의 1, 2번 작업을 진행할 프론트 컨트롤러만 생겨났다.

View 분리 - v2
모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있고, 깔끔하지 않다.
아래의 중복 코드를 깔끔하게 분리하기 위해 별도로 뷰를 처리하는 객체를 만든다.
String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);

*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);
}
}
MyView.render() 를 실행하기 위한 코드를 만든다.
RequestDispatcher 의 forward() 메서드는 대상 자원으로 제어를 넘기는 역할을 한다. 브라우저에서 /a.jsp 로 요청했을 때 /a.jsp 에서 forward() 를 실행하여 /b.jsp 로 제어를 넘길 수 있다. 제어를 넘겨받은 /b.jsp 는 처리 결과를 최종적으로 브라우저에 출력한다. 브라우저 입장에서는 /a.jsp 를 요청했지만 받은 결과는 /b.jsp 가 된다. 이 모든 것은 HTTP 리다이렉트 방식과는 달리 하나의 HTTP 요청(Request) 범위 안에서 동작이 이루어 진다.
Model 추가 - v3
컨트롤러로 넘어가는 서블릿 기술들을 제거하는 단계이다.
이때 request 객체를 Model 로 사용하는 대신에 별도의 Model 객체를 만들어서 반환한다.
이 방법은 구현 코드가 매우 단순해지고, 테스트 코드 작성 또한 쉬워진다.

ModelView
지금까지 컨트롤러에서 서블릿에 종속적인 HttpServletRequest 를 사용했다. 그리고 Model 도 request.setAttribute() 를 통해 데이터를 저장하고 뷰에 전달했다. 서블릿의 종속성을 제거하기 위해 Model 을 직접 만들고, 추가로 View 이름까지 전달하는 객체를 만들어야 한다.
v3 구조에서는 컨트롤러에서 HttpServletRequest 를 사용할 수 없다. 따라서 직접 request.setAttribute() 를 호출할 수 없기 때문에 Model 이 별도로 필요하다.
@Getter @Setter
public class ModelView {
private String viewName;
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName) {
this.viewName = viewName;
}
}
ControllerV3
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
HttpServletRequest 가 제공하는 파라미터는 프론트 컨트롤러가 paramMap 에 담아서 호출해주면 된다. 응답 결과로 뷰 이름과 뷰에 전달할 Model 데이터를 포함하는 ModelView 객체를 반환하면 된다.
FrontControllerServletV3
@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
// key: 매핑 URL, value: 호출될 컨트롤러
private Map<String, ControllerV3> controllerV3Map = new HashMap<>();
// 초기화
public FrontControllerServletV3() {
controllerV3Map.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
controllerV3Map.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
controllerV3Map.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 = controllerV3Map.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// paramMap
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
String viewName = mv.getViewName(); // 논리이름 new-form
// "/WEB-INF/views/new-form.jsp"
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> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator()
.forEachRemaining(paramName
-> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
*MyView
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);
}
v3 부터는 뷰 객체의 render() 는 model 정보도 함께 받는다.
단순하고 실용적인 컨트롤러 - v4
v3 컨트롤러는 잘 설계된 컨트롤러이다. 그러나 ModelView 객체를 생성하고 반환해야 하는 부분에 있어서는 실용성이 떨어진다.
이를 효율적으로 개선한게 v4 버전이다.

기본적인 구조는 v3 와 같지만, 컨트롤러가 ModelView 를 반환하지 않고, ViewName 만 반환한다.
ControllerV4
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
인터페이스에 ModelView 가 없다. model 객체는 파라미터로 전달되기 때문에 그냥 사용하면 되고, 결과로 뷰 이름만 반환해주면 된다.
FrontControllerServletV4
@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
private Map<String, ControllerV4> controllerV4Map = new HashMap<>();
public FrontControllerServletV4() {
controllerV4Map.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
controllerV4Map.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
controllerV4Map.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 = controllerV4Map.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> paramMap = createParamMap(request);
Map<String, Object> model = new HashMap<>(); // 추가
String viewName = controller.process(paramMap, model);
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> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator()
.forEachRemaining(paramName
-> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
v3 와 거의 비슷하다.
모델 객체 전달
Map<String, Object> model = new HashMap<>();
모델 객체를 프론트 컨트롤러에서 생성해서 넘겨준다. 컨트롤러에서 모델 객체에 값을 담으면 여기에 그대로 담겨 있게 된다.
참고)
1. 김영한 님의 '스프링 MVC 1편 - 백엔드 웹 개발 핵심'에서 MVC 프레임워크 만들기
2. 코끼리를 냉장고에 넣는 방법 :: [서블릿/JSP] RequestDispatcher란. RequestDispatcher로 forward() 하기 (tistory.com)
[서블릿/JSP] RequestDispatcher란. RequestDispatcher로 forward() 하기
참고글 [서블릿/JSP] JSP 리다이렉트로 페이지 이동시키기 [서블릿/JSP] JSP 기본객체 종류 [HTTP] 리다이렉트(Redirect)란? RequestDispatcher란 RequestDispatcher는 클라이언트로부터 최초에 들어온 요청을 JSP..
dololak.tistory.com
'공부 > Spring' 카테고리의 다른 글
#스프링 MVC - 구조 이해 (0) | 2022.01.27 |
---|---|
#MVC 프레임워크 만들기 - 유연한 컨트롤러 - v5 (0) | 2022.01.27 |
MVC 패턴 (0) | 2022.01.26 |
기본 생성자를 선언하는 이유 (0) | 2022.01.25 |
HTTP 응답 데이터 (0) | 2022.01.25 |