인프런 김영한 님의 스프링 강의이며, 섹션 4 - MVC 프레임워크 만들기를 정리한다.
이전 시간의 프론트 컨트롤러를 개선하면서 진행한다.
자세한 설명이 궁금하면 수강을 권장한다.
단순하고 실용적인 컨트롤러 - v4
v3는 많이 개선이 되었지만, 사용자가 ModelView 객체를 생성하고 반환하는 행위가 반복이 된다.
좋은 프레임워크는 아키텍처도 중요하지만, 개발자가 단순하고 편리(실용성)하게 사용할 수 있어야 한다.
ModelView 대신 ViewName만 반환되게 개선해보자.
public interface ControllerV4 {
/**
* @param paramMap
* @param model
* @return viewName
*/
String process(Map<String, String> paramMap, Map<String, Object> model);
}
String으로 viewName을 반환한다. 그리고 파라미터에 Map<String, Object> model을 추가했다.
ModelView의 model을 저기에 담아준다.
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);
return "save-result";
}
}
public class MemberListControllerV4 implements ControllerV4 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
model.put("members", memberRepository.findAll());
return "members";
}
}
public class MemberFormControllerV4 implements ControllerV4 {
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
return "new-form";
}
}
컨트롤러마다 ModelView 대신 viewName을 반환하는 구조면서, 기존의 model 객체는 넘겨온 model로 대체했다.
프론트컨트롤러도 간단히 수정하면 끝난다.
@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
...
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV4.service");
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);
}
...
}
이전에는 ModelView를 받아서 다시 값을 꺼내는 과정이 생략되어 조금 더 깔끔하고 개발자 입장에서 개발이 더 편해진 구조가 되었다.
프레임워크나 공통 기능이 수고로워야 사용하는 개발자가 편리해진다.
유연한 컨트롤러 - v5
이전까지 컨트롤러의 구조가 정해진 상태였다.
// V4 방식만 호출할 수 있다.
private Map<String, ControllerV4> controllerMap = new HashMap<>();
V4가 아닌 다른 버전인 V1,V2,V3를 모두 사용할 방법이 필요하고, 이를 구현할 방법은 어댑터 패턴이다
어댑터 패턴
110v, 220v 전기콘센트를 다른 전기나 기계 장치를 연결해주는 도구가 어댑터다. (핸드폰 충전기, 노트북 충전기를 생각하라)
개발에서 어댑터 패턴 클래스를 사용하여 클라이언트가 원하는 인터페이스로 연결해준다.
즉, 클라이언트는 어댑터를 호출하면 어댑터는 클라이언트가 원하는 인터페이스를 대신 호출한다.
이전과 다르게 Controller 대신 Handler 용어를 사용한다.
핸들러 : 컨트롤러뿐만 아니라 다른 개념도 처리 가능해서 핸들러라 부른다.
핸들러 어댑터 : 다양한 컨트롤러나 다른 개념들을 호출할 수 있는 어댑터 역할을 한다.
핸들러어댑터를 구현해보자
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
supports는 핸들러를 지원하는지 판단한다.
handle는 해당되는 핸들러를 실행하고 ModelView로 반환해야 하는데
V3는 ModelView를 반환하게 짜였지만 V4는 viewName만 반환하는 구조다.
V4도 ModelView로 반환하게 수정해야 할까?
우선 V3 핸들러를 만들어보자.
public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV3);
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
ControllerV3 controller = (ControllerV3) handler;
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
return mv;
}
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;
}
}
실행 순서는 다음과 같다.
1) supports를 통해 어댑터가 지원하는 인터페이스인지 확인한다.
2) 클라이언트의 호출받아 기존 프론트컨트롤러V3 대체한다.
3) 결과를 ModelView에 담아 반환한다.
이제 모든 걸 처리할 수 있는 프론트컨트롤러V5를 작성하자
@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() {
initHandlerMappingMap();
initHandlerAdapters();
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
}
private void initHandlerMappingMap() {
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());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV5.service");
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 MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}
이전까지 Map<String, ControllerV4> 방식으로 컨트롤러를 고정시켜 사용했지만 Map<String, Object> 로 수정되었다.
변수 명도 controllerMap -> hamdlerMappingMap으로 변경되었다.
컨트롤러뿐만 아니라 어댑터가 지원할 수 있는 모든 핸들러를 처리할 수 있다는 의미다.
List<MyHandlerAdapter>를 통해 프론트컨트롤러V5가 제공하는 어댑터를 등록한다.
등록된 어댑터는 반복문을 통해 지원이 가능한 어댑터(supports로 판단)를 찾아 반환하고
어댑터를 통해 클라이언트가 원하는 인터페이스를 실행한다.
문제없이 정상적으로 작동한다.
이제 V4를 적용해보자.
우선 V4를 매핑할 수 있게 프론트컨트롤러V5를 살짝 수정해보자.
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
...
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter());
}
private void initHandlerMappingMap() {
// V3
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());
// V4
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());
}
...
}
수정은 단순하다. handlerAdapters와 handlerMappingMap에 V4 관련 어댑터와 컨트롤러를 추가하면 끝이다.
이제 V4어댑터를 작성해보자.
public class ControllerV4HandlerAdapter implements MyHandlerAdapter {
...
@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;
}
...
}
기존 컨트롤러V4는 viewName을 반환하는 구조로 ModelView가 아니다.
V4어댑터는 클라이언트가 ModelView를 받아 처리할 수 있도록, ModelView 객체를 생성하여 반환하는 과정이 추가되어있다.
말 그대로 어댑터의 진정한 역할이다.
'교육 및 인강 > 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 카테고리의 다른 글
스프링 MVC 이해 - 시작하기, 컨트롤러 통합, 실용적인 방식 (0) | 2021.07.19 |
---|---|
스프링 MVC 이해 - 전체 구조, 핸들러 매핑과 핸들러 어댑터, 뷰 리졸버 (0) | 2021.07.13 |
MVC 프레임워크 만들기 - View 분리(v2), Model 추가(v3) (0) | 2021.07.05 |
MVC 프레임워크 만들기 - 프론트 컨트롤러 패턴 소개, 도입 (0) | 2021.07.05 |
MVC 패턴 - 개요, 적용, 한계 (0) | 2021.06.29 |