Spring 예제#2 ( Adapter Handler, FrontController )
앞선 FrontController 예제가 하나의 Controller 인터페이스만 처리할 수 있다는 한계를 어댑터 패턴으로 해결하는 예제다. 핵심은 MyHandlerAdapter 인터페이스(supports/handle)를 두고, 핸들러 타입(Controller, Controller2 등)마다 전용 어댑터를 만들어 FrontController가 getHandlerAdapter()로 알맞은 어댑터를 찾아 위임하도록 하는 구조다. 이는 실제 Spring MVC의 HandlerAdapter 설계와 동일한 아이디어다.
실행 환경
@WebServlet어노테이션 기반 등록 방식이 사용되어 Servlet 3.0 이상을 전제로 한다. 구체적인 Servlet/Tomcat 버전은 코드상 명시되어 있지 않다.
Adapter handler
Adapter Handler 사용이유
- 한가지 로직을 실행시키는데에 있어, A/B 경우를 골라야한다면 어떻게 처리해야할지에 대한 의문
- frontcontroller 의 경우 한가지 방식의 컨트롤러 인터페이스만 사용 가능
Adapter Handler 도식화
Client
└─▶ FrontController.service(request, response)
└─▶ getHandler(request) // handlerMappingMap 에서 handler(=controller) 조회
handler : Controller 객체 or Controller2 객체 (타입이 다를 수 있음)
└─▶ getHandlerAdapter(handler) // handlerAdapters 순회하며 supports(handler)==true 인 어댑터 탐색
├─ ControllerHandlerAdapter (supports: handler instanceof Controller)
└─ Controller2HandlerAdapter (supports: handler instanceof Controller2)
└─▶ adapter.handle(request, response, handler)
└─▶ (형변환 후) controller.process(paramMap) → ModelView 반환
└─▶ viewResolver(mv.getViewName()) → MyView
└─▶ view.render(mv.getModel(), request, response) // JSP forward수정해야하는 부분
- FrontController class에서 Controller를 객체 그대로 받는 경우가 수정되어야한다
(Before)
private Map<String, Controller> controllerV3Map = new HashMap<>();
(After)
MyHandlerAdapter adapter = getHandlerAdapter(handler);
ModelView mv = adapter.handle(request, response, handler);
Adapter Interface 생성
- handler == controller
- adapter는 실제 controller 호출 후 ModelView 반환
public interface MyHandlerAdapter{
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException,IOException;
}
}Adapter 생성
- 각각의 인터페이스의 맞는 어뎁터 생성
- Controller , Controller2
public class ControllerHandlerAdapter implements MyHandlerAdapter{
@Override
public boolean supports(Object handler){
return (handler instanceOf Controller);
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
{
// 해당 컨트롤러 인터페이스로 캐스팅
Controller controller = (Controller) 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;
}
}
public class Controller2HandlerAdapter implements MyHandlerAdapter{
@Override
public boolean supports(Object handler)
{
return (handler instnaceOf Controller2);
}
@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler){
Controller2 controller = (Controller2) 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;
}
}FrontController 수정
- Controller 와 Controller2의 map을 준비
- adapter를 이용한 handler(==controller) 호출
//adapter 하위로 들어오는 경우 모두 타겟팅
@WebServlet(urlPatterns="/main/adapter/*")
public class FrontController extends HttpServlet{
private final Map<String, Object> handlerMappintMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
public FrontController(){
initHandlerMappingMap();
initHandlerAdpaters();
}
private void initHandlerMappingMap(){
handlerMappingMap.put("/main/adapter/controller/new-form", new MemberFormController());
handlerMappingMap.put("/main/adapter/controller/save", new MemberSaveController());
handlerMappingMap.put("/main/adapter/controller/members", new MemberListController());
handlerMappingMap.put("/main/adapter/controller2/new-form", new MemberFormController2());
handlerMappingMap.put("/main/adapter/controller2/save", new MemberSaveController2());
handlerMappingMap.put("/main/adapter/controller2/members", new MemberListController2());
}
private initHandlerAdapters(){
handlerAdapters.add(new ControllerHandlerAdapter());
handlerAdapters.add(new Controller2HandlerAdapter());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletExcetption, 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 req) {
String requestURI = req.getRequestURI();
Object handler = handlerMappingMap.get(requestURI);
return handler;
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}정리
- Controller1 과 Controller2의 경우, 같은 로직의 이름만 다르게해서 Adapter를 적용해보았다
- 하지만, 인터페이스 구조가 다를경우 ControllerHandlerAdapter가 서로 다른 구조였을 것이다 ( handle() 부분 )
- 이렇듯, adapter를 사용하면 “변압기”처럼 원하는 것을 끼우며 사용할 수있다
- 더 나아가, Annotaion을 지원하게 된다면 스프링과 유사하게 프레임워크를 구성할 수 있게 된다.