김영한님의 스프링 MVC 1편 강의를 듣고 복습 겸 정리하는 포스팅입니다!!
아직 공부하는 중인 학생이라 부족한 부분이 있을 수 있습니다.
혹시라도 틀린 부분이 있다면 언제든지 댓글로 남겨주세요. 감사합니다 🙇♂️
섹션 5. 유연한 컨트롤러1 - V5
[이전 버전]
https://lyjduswls.tistory.com/57
[스프링 MVC] #7
김영한님의 스프링 MVC 1편 강의를 듣고 복습 겸 정리하는 포스팅입니다!!아직 공부하는 중인 학생이라 부족한 부분이 있을 수 있습니다. 혹시라도 틀린 부분이 있다면 언제든지 댓글로 남겨주세
lyjduswls.tistory.com
한 프로젝트 내에서 어떤 개발자는 ControllerV3 방식, 다른 개발자는 ControllerV4방식을 선호하는 경우가 있다.
private Map<String, ControllerV4> controllerMap = new HashMap<>();
위와 같이 ControllerV4 타입으로 고정되어 있기 때문에, ControllerV3를 넣으려고 하면 타입 오류가 발생한다.
어댑터 패턴
어댑터 패턴을 도입하면 다양한 컨트롤러 인터페이스를 하나의 프론트 컨트롤러에서 유연하게 처리할 수 있다.
프론트 컨트롤러는 어댑터를 통해 컨트롤러의 타입과 무관하게 동일한 방식으로 동작시킨다.
V5 구조
- 클라이언트가 HTTP 요청을 전송한다.
- 프론트 컨트롤러가 핸들러 매핑 정보를 조회한다.
- 조회된 핸들러를 처리할 수 있는 핸들러 어댑터를 목록에서 찾는다.
- 프론트 컨트롤러가 어댑터의 handle(request, response, handler)메서드를 호출해 실제 컨트롤러를 실행한다.
- 핸들러 어댑터는 ModelView 객체를 생성해 반환한다.
- 프론트 컨트롤러는 ModelView에서 논리 뷰 이름을 꺼내어, 뷰 리졸버를 호출한다.
- 뷰 리졸버(viewResolver)는 논리 뷰 이름을 물리 경로로 변환하고, 해당 경로를 가진 MyView 객체를 반환한다.
- 프론트 컨트롤러가 MyView.render(model)을 호출한다.
- MyView 내부에서 JSP로 forward하여 HTML 응답을 생성하고, 이를 클라이언트에게 전달한다.
프론트 컨트롤러는 핸들러 어댑터의 인터페이스에만 의존하고 있다.
구현 클래스가 뭐가 들어오든 상관 없이, 프론트 컨트롤러의 코드를 바꿀 필요가 없다.
핸들러 어댑터
- 프론트 컨트롤러와 실제 컨트롤러 사이에 중간 역할자 역할을 하는 핸들러 어댑터가 추가되었다.
- 핸들러 어댑터 덕분에 다양한 형태의 컨트롤러를 호출할 수 있다.
핸들러
- 컨트롤러의 이름을 더 넓은 범위인 핸들러로 변경했다.
- 어댑터가 있기 때문에 컨트롤러의 개념 뿐만 아니라 어떠한 것이든 해당하는 종류의 어댑터만 있으면 다 처리할 수 있다.
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;
}
MyHandlerAdapter는 프론트 컨트롤러와 다양한 컨트롤러(핸들러) 사이를 연결해주는 중간 어댑터 역할을 한다.
1. boolean supports(Object handler)
- 어댑터가 해당 컨트롤러를 처리할 수 있는지 판단하는 메서드다.
- 처리 가능한 컨트롤러라면 True, 그렇지 않다면 False를 반환한다.
2. ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
- 어댑터는 실제 컨트롤러를 실행하고, 결과를 ModelView 형태로 반환해야 한다.
- 실제 컨트롤러가 ModelView를 반환하지 못하면, 어댑터가 ModelView를 직접 생성해서라도 반환해야 한다.
FrontControllerServletV5
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.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() {
initHandlerMappingMap();
initHandlerAdapters();
}
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());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
}
@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 Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler = " + handler);
}
private static MyView viewResolver(String viewName) {
MyView view = new MyView("/WEB-INF/views/" + viewName + ".jsp");
return view;
}
}
public FrontControllerServletV5() {
initHandlerMappingMap();
initHandlerAdapters();
}
- 이전에는 컨트롤러에서 직접 매핑해서 사용했으나, 이제는 어댑터를 사용하기 때문에 컨트롤러 뿐만 아니라 어댑터가 지원하기만 하면 어떤 것이라도 URL에 매핑해서 사용할 수 있다.
- 생성자는 핸들러 매핑과 어댑터를 초기화한다.
매핑 정보
ControllerV4
private Map<String, ControllerV4> controllerMap = new HashMap<>();
기존의 ControllerV4에서는 매핑정보의 값이 ControllerV4로 고정되어 있었다.
ControllerV5
private final Map<String, Object> handlerMappingMap = new HashMap<>();
매핑 정보의 값이 ControllerV3, ControllerV4와 같은 인터페이스에서 아무 값이나 받을 수 있는 Object로 변경되었다.
핸들러 매핑
Object handler = getHandler(request);
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
요청 URL을 기반으로 매핑된 핸들러(ControllerV3, ControllerV4 등)을 찾아 반환한다.
핸들러를 처리할 수 있는 어댑터 조회
MyHandlerAdapter adapter = getHandlerAdapter(handler);
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler = " + handler);
}
handler를 처리할 수 있는 어댑터를 adapter.supports(handler)를 통해서 찾는다.
handler가 ControllerV3 인터페이스를 구현했다면, ControllerV3HandlerAdapter 객체가 반환된다.
어댑터 호출
ModelView mv = adapter.handle(request, response, handler);
어댑터가 핸들러를 실행한 후, 결과를 ModelView 형태로 표준화하여 반환한다.
정리
V5 구조는 어댑터 패턴을 적용하여, 하나의 프론트 컨트롤러로 다양한 형태의 컨트롤러를 유연하게 처리할 수 있는 구조이다.
컨트롤러의 구조가 달라도, 어댑터만 구현되어 있으면 모두 동일한 방식으로 연결하고 실행할 수 있다.
섹션 5. 유연한 컨트롤러2 - V5
이번 강의에서는 V3 뿐만 아니라 V4까지 포함하게 수정하였다.
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() {
initHandlerMappingMap();
initHandlerAdapters();
}
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());
//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());
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
handlerAdapters.add(new ControllerV4HandlerAdapter()); // V4를 처리할 수 있는 어댑터
}
@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 Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler = " + handler);
}
private static MyView viewResolver(String viewName) {
MyView view = new MyView("/WEB-INF/views/" + viewName + ".jsp");
return view;
}
}
ControllerV4HandlerAdapter
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.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);
HashMap<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(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV4);
}
handler가 ControllerV4인 경우에만 처리하는 어댑터이다.
실행 로직
ControllerV4 controller = (ControllerV4) handler;
Map<String, String> paramMap = createParamMap(request);
Map<String, Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model);
프론트 컨트롤러에서 실행했던 V3 버전과는 다르게 이제는 어댑터에서 객체를 생성하고 해당 컨트롤러를 호출한다.
어댑터 변환
ModelView mv = new ModelView(viewName);
mv.setModel(model);
return mv;
어댑터에서 ModelView를 생성해 반환한다.
어댑터와 ControllerV4
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
어댑터가 호출하는 ControllerV4 는 실행 후 논리 뷰의 이름만 반환한다.
public interface MyHandlerAdapter {
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
MyHandlerAdapter의 handle() 메서드는 뷰의 이름이 아니라 ModelView로 만들어 형식을 맞추어 반환한다.
'Spring Study' 카테고리의 다른 글
[스프링 MVC] #10 (0) | 2025.05.17 |
---|---|
[스프링 MVC] #9 (0) | 2025.05.14 |
[스프링 MVC] #7 (0) | 2025.05.04 |
[스프링 MVC] #6 (1) | 2025.05.04 |
[스프링 MVC] #5 (1) | 2025.05.03 |