지난 시간에 만들었던 V3 컨트롤러
는 서블릿 종속성과 뷰 이름 중복도 제거한 잘 설계된 컨트롤러이다.
하지만 사용할 때 매번 ModelView
객체를 생성하고 반환해야 하는 부분이 조금은 번거롭다.😭
이번에는 v3
를 조금 변경해서 실제 구현하는 개발자들이 매우 편리하게 개발할 수 있는 v4 버전을 개발해보자!
V4
구조ModelView
를 반환하지 않고, ViewName
만 반환한다!ControllerV4
package hello.servlet.web.frontcontroller.v4;
import java.util.Map;
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
model
객체는 파라미터로 전달되므로,
따로 ModelView
를 생성하지 않고 viewName
만 반환해주면 된다.
ControllerV4
를 구현하는 3개의 컨트롤러 모두 이제 뷰의 논리 이름(new-form
등)만 반환하면 된다. → 매번 객체 생성하는 비용을 줄일 수 있다. FrontControllerServletV4
(전체 코드는 위의 링크 참고)
Map<String, Object> model = new HashMap<>();
Model
객체를 프론트 컨트롤러에서 생성해서 넘겨준다.Model
에 데이터를 담아서 전달할 수 있다. → ModelView
를 생성하지 않아도 된다!String viewName = controller.process(paramMap, model);
MyView view = viewResolver(viewName);
지금까지 V1
부터 V4
까지 다양한 방법으로 컨트롤러를 개발해왔다.
그런데 만약 한 팀 내에서 어떤 사람👩🏻은 ControllerV3
방식으로, 어떤 사람👨🏻은 ControllerV4
방식으로 개발하고 싶어 한다면 어떻게 해야 할까?
🙋🏻♀️: 그냥 다 같이 써요!
👩🏻🏫: 지금은 안돼!
ControllerV3
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
ControllerV4
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
V3
와 V4
는 process
메서드만 봐도 반환 타입과 인자 값의 개수와 타입이 다른 것을 확인할 수 있다. 마치 110V와 220V 전기 콘센트처럼, V3
와 V4
는 호환이 불가능하다.
이런 상황에서 필요한 것이 바로 어댑터이다!
어댑터 패턴을 이용하면 다른 타입의 컨트롤러를 혼용해서 쓸 수 있다. 지금부터 어댑터 패턴을 이용하여 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 바꿔보자!
V5
구조핸들러 어댑터: 프론트 컨트롤러와 핸들러(컨트롤러) 중간에 핸들러 어댑터가 추가되었다.
→ 이 어댑터를 통해 다양한 종류의 컨트롤러를 호출할 수 있다.
핸들러: 컨트롤러의 이름을 더 넓은 범위인 핸들러로 변경했다. 이제 어댑터가 있기 때문에 꼭 컨트롤러의 개념 뿐만 아니라 어떠한 것이든 해당하는 종류의 어댑터만 있으면 다 처리할 수 있기 때문이다.
MyHandlerAdapter
- 어댑터용 인터페이스package hello.servlet.web.frontcontroller.v5;
import hello.servlet.web.frontcontroller.ModelView;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.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;
}
boolean supports(Object handler);
handler
는 컨트롤러를 말한다.ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
ModelView
를 반환해야 한다.ModelView
를 반환하지 못하면, 어댑터가 ModelView
를 직접 생성해서라도 반환해야 한다.ControllerV3HandlerAdapter
ControllerV3
를 지원하는 어댑터를 구현해보자!
public boolean supports(Object handler) {
return (handler instanceof ControllerV3);
}
ControllerV3
을 처리할 수 있는 어댑터인지 판단한다.ControllerV3 controller = (ControllerV3) handler;
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
return mv;
handler
를 ControllerV3
로 변환한 뒤, V3
형식에 맞도록 호출한다.ControllerV3
는 ModelView
를 반환하므로 그대로 ModelView
를 반환하면 된다.FrontControllerServletV5
(전체 코드는 위의 링크 참고)
📌 컨트롤러
(Controller)
→ 핸들러(Handler)
- 이전에는 컨트롤러를 직접 매핑해서 사용했다.
- 이제는 어댑터를 사용하기 때문에 컨트롤러 뿐만 아니라 어댑터가 지원하기만 하면, 어떤 것이라도 URL에 매핑해서 사용할 수 있다.
- 그래서 이름을 컨트롤러 → 핸들러로 변경했다!
public FrontControllerServletV5() {
initHandlerMappingMap(); //핸들러 매핑 초기화
initHandlerAdapters(); //어댑터 초기화
}
생성자는 핸들러 매핑과 어댑터를 초기화(등록)한다.
private final Map<String, Object> handlerMappingMap = new HashMap<>();
매핑 정보의 값이 ControllerV3
, ControllerV4
같은 인터페이스에서 아무 값이나 받을 수 있는 Object
로 변경되었다.
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI);
}
핸들러 매핑 정보인 handlerMappingMap
에서 URL에 매핑된 핸들러(컨트롤러) 객체를 찾아서 반환한다.
MyHandlerAdapter adapter = getHandlerAdapter(handler)
핸들러를 처리할 수 있는 어댑터를 adapter.supports(handler)
를 통해 찾는다.
ModelView mv = adapter.handle(request, response, handler);
handle(request, response, handler)
메서드를 통해 실제 어댑터가 호출된다.handler
(컨트롤러)를 호출하고 그 결과를 어댑터에 맞추어 반환한다.ControllerV3HandlerAdapter
의 경우 어댑터의 모양과 컨트롤러의 모양이 유사해서 변환 로직이 단순하다.이번에는
ControllerV4
를 사용할 수 있도록 추가해보자!
FrontControllerServletV5
private void initHandlerMappingMap() {
...
//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 추가
}
ControllerV4
용 경로(key)와 컨트롤러를 handlerMappingMap
에 추가해준다.ControllerV4
도 호환이 되도록 해주는 어댑터를 handlerAdapter
콜렉션에 추가해준다. ControllerV4HandlerAdapter
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);
handler
를 ControllerV4
로 캐스팅하고, paramMap
, model
을 만들어서 해당 컨트롤러를 호출한 뒤, viewName
을 반환 받는다.
ModelView mv = new ModelView(viewName);
mv.setModel(model);
return mv;
어댑터가 호출하고 ControllerV4
는 뷰의 이름을 반환한다. ControllerV4
는 뷰의 이름을 반환했지만, 어댑터는 이것을 ModelView
로 만들어서 형식을 맞추어 반환한다.
🤸🏻♀️ 마치 110v 전기 콘센트를 220v 전기 콘센트로 변경하듯이!
ControllerV4
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
public interface MyHandlerAdapter {
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
v1
- 프론트 컨트롤러 도입v2
- View
분류v3
- Model
추가v4
- 단순하고 실용적인 컨트롤러ModelView
를 직접 생성해서 반환하지 않도록 편리한 인터페이스 제공v5
- 유연한 컨트롤러