개발일기
MVC 프레임워크 만들기 (V3, V4, V5) 본문
V3
시작하기에 앞서 V3의 구조부터 살펴보자
해당 구조는 V3의 구조이며 기존에 MyView구조에서 ModelView 구현 클래스와 viewResolver가 추가되었다.
이때, ModelView를 사용하면 기존에 request.setAttribute를 통해 호출하던 부분을 Servlet종속성을 제거하여 View에 객체를 전달할 수 있다.
이제 부터 코드를 살펴보자.
ModelView
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;
} }
viewName을 받기 위한 String 타입과 model을 받기 위한 Map을 필드값으로 가지고있는 ModelView를 만들어주었다. 또한 각각에 객체에 접근하기 위해 getter와 setter를 만들어주었다.
ControllerV3
package hello.servlet.web.frontcontroller.v3;
import hello.servlet.web.frontcontroller.ModelView;
import java.util.Map;
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
해당 코드는 MyView로 받던 process메서드 리턴 타입을 ModelView로 바꿔주었다. Map을 넘기면 해당 부분을 map에서 꺼내어 view에서 사용할 수 있다.
해당 부분은 실무에서도 자주사용되는 방식으로 실무에서 따로 프론트 개발자가 없다면 JSON객체로 넘겨주는 방식이 아닌 model에 map을 넘겨서 사용하는 방식이 사용되기때문에 해당 방식으로 진행하는 회사라면 이런 방식의 코드를 볼 수 있다. 단, Spring에서 제공하는 것을 통하여 코드가 개발 되어 있을 것이다. .
MemberListControllerV3
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> paramMap) {
List<Member> members = memberRepository.findAll();
ModelView mv = new ModelView("members");
mv.getModel().put("members",members);
return mv;
}
}
model.view에서 model에 "members" key에 members를 담는 모습을 볼 수 있다.
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 jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.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 {
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());
controllerMap.put("/front-controller/v3/members", new MemberListControllerV3());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
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);
}
private static MyView viewResolver(String viewName) {
MyView view = new MyView("/WEB-INF/views/" + viewName + ".jsp");
return view;
}
private static Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator().forEachRemaining(paraName -> paramMap.put(paraName, request.getParameter(paraName)));
return paramMap;
}
}
해당 코드를 통해서 ParamMap에 해당 하는 부분에서 model이 paramMap으로 넘겨주었다.
ControllerV4
V4의 컨셉은 개발자가 단순하고 편리하게 사용할 수 있게 코드를 개선하는 것이다. model을 사용하는 곳을 조금 더 개발자 친화적으로 변경하여 구성
2, 3 에서 호출하는 부분에서 model이 추가되었고 ModelView로 리턴되던 process가 String타입의 viewName이 리턴되어 질 것이다. 확실히 이렇게하면 model을 꺼낼 필요가 없어 조금 더 편리하게 코드를 구성할 수 있다.
ControllerV4
package hello.servlet.web.frontcontroller.v4;
import java.util.Map;
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
위에서 말한 내용이 ControllerV4에 적용 되어 있다.
FrontController
package hello.servlet.web.frontcontroller.v4;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
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 hello.servlet.web.frontcontroller.v4.controller.MemberFormControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberSaveControllerV4;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.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 {
private Map<String, ControllerV4> controllerMap = new HashMap<>();
public FrontControllerServletV4() {
controllerMap.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
controllerMap.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
controllerMap.put("/front-controller/v4/members", new MemberListControllerV4());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV4 controller = controllerMap.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 static MyView viewResolver(String viewName) {
MyView view = new MyView("/WEB-INF/views/" + viewName + ".jsp");
return view;
}
private static Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator().forEachRemaining(paraName -> paramMap.put(paraName, request.getParameter(paraName)));
return paramMap;
}
}
viewResolver메서드를 통해서 중복되는 URI 패턴을 제거해주었다.
MemberListControllerV4
package hello.servlet.web.frontcontroller.v4.controller;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
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";
}
}
V5
FrontController는 V3, V4, V5 등 어떤 Controller가 오든 유연하게 사용하기 위해 핸들러 어댑터를 사용함.
이때, 핸들러 어댑터는 돼지코 같은 존재임 어디서든 꽂아서 사용할 수 있게 만들어줌
package hello.servlet.web.frontcontroller.v5;
import hello.servlet.web.frontcontroller.ModelView;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
MyHandlerAdapter
package hello.servlet.web.frontcontroller.v5;
import hello.servlet.web.frontcontroller.ModelView;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
supports는 해당 handler의 Controller가 V3, V4 인지 판별하는 메소드이다. 그래서 boolean타입으로 선언해주었다.
ModelView는 실질적으로 Modle 데이터 viewName을 ModelView에 담아 반환해주는 메서드이다.
package hello.servlet.web.frontcontroller.v5;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
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 hello.servlet.web.frontcontroller.v4.controller.MemberFormControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberSaveControllerV4;
import hello.servlet.web.frontcontroller.v5.adapter.ControllerV3HandlerAdapter;
import hello.servlet.web.frontcontroller.v5.adapter.ControllerV4HandlerAdapter;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@WebServlet(name="frontControllerServletV5", urlPatterns="/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
private final Map<String, Object> handlerMappingMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
public FrontControllerServletV5() {
initHandlerMapping();
initHandlerAdapters();
}
private void initHandlerMapping() {
handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object handler = getHandler(request);
if(handler == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return ;
}
MyHandlerAdapter adapter = getHandlerAdapter(handler);
ModelView mv = adapter.handle(request, response, handler);
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
view.render(mv.getModel(), request, response);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if(adapter.supports(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler);
}
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
private static MyView viewResolver(String viewName) {
MyView view = new MyView("/WEB-INF/views/" + viewName + ".jsp");
return view;
}
}
생성자를 생성함과 동시에 mappging 메서드들을 매핑해준다. 어댑터를 통해서 해당 어댑터가 존재한다면 반환 없으면 Exception을 터트려주었다.
package hello.servlet.web.frontcontroller;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
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));
}
}
render는 Model을 파라미터로 받는 부분으로 render를 해주었으며 render되기전 modelToRequestAttribute의 메서드를 통해서 model에 있는 데이터들을 setAttribute에 담아서 forward해주었다.
ControllerV4HandlerAdapter.java
package hello.servlet.web.frontcontroller.v5.adapter;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v4.ControllerV4;
import hello.servlet.web.frontcontroller.v5.MyHandlerAdapter;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class ControllerV4HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV4);
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
ControllerV4 controller = (ControllerV4) handler;
Map<String, String> paramMap = createParamMap(request);
Map<String, Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model);
ModelView mv = new ModelView(viewName);
mv.setModel(model);
return mv;
}
private static Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator().forEachRemaining(paraName -> paramMap.put(paraName, request.getParameter(paraName)));
return paramMap;
}
}
(ControllerV4)로 캐스팅을 해 줄 수 있는 이유는 supports를 통해서 V4인지 이미 검증을 완료했기 때문에 가능하다.
'Spring > (김영한님)스프링 MVC 1편 - 백엔드 웹 개발 핵심기술' 카테고리의 다른 글
Spring Boot MVC 기본기능 모음집 회고- 2편 (1) | 2024.03.27 |
---|---|
Spring Boot MVC 기본기능 모음집 회고- 1편 (0) | 2024.03.27 |
MVC 프레임워크 만들기 정리 및 회고 (V1, V2) (0) | 2024.03.06 |
서블릿, JSP , MVC 패턴 회고 및 정리 (0) | 2024.03.02 |
Session2. 서블릿 정리및 회고 ( request, response) (2) | 2024.02.27 |