[Spring MVC] MVC Framework V3
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 구조
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로 렌더링 한다.
댓글남기기