Front Controller (5)
지금까지 V1부터 V4까지의 점진적인 방식으로 좀더 정형화되고 중복을 줄이며 간단하게 개발자 친화적으로 발전하는 코드를 구현해왔다. 그렇다고 무조건 V4가 좋은 코드라고 할 수 없고 어떤때는 V1방식으로 어떤때는 V3방식으로 개발을 해야하는 상황이 올 수 있다. 그렇다면 매번 각각의 Front-Controller에서 V1을 V3로 모두 바꿔야한다면 이만큼 불편한 일이 없을 것이다. 지금까지 우리가 만들어온 인터페이스들 ControllerV3, ControllerV4등등은 모두 완전히 다른 방식이므로 호환이 되지 않는다. 이럴때 사용하는 사용하는 것이 어댑터 패턴이다. 이 어댑터 패턴을 이용해서 Front-Controller가 다양한 인터페이스의 컨트롤러를 처리할 수 있게 만들어 보자.
핸들러 어댑터 : 중간에 어댑터 역할을 해주는 핸들러 어댑터가 추가 되었는데, 핸들러 어댑터 덕분에 우리는 다양한 종류의 컨트롤러를 호출 할 수 있다.
핸들러 : 컨트롤러의 이름을 좀 더 넓은 개념의 핸들러로 변경했다. 그 이유는 이제 어댑터가 있기 때문에 꼭 컨트롤러의 개념 뿐만 아니라 어떠한 것이든 해당하는 종류의 어댑터만 있으면 다 처리할 수 있기 때문이다
먼저 핸들러 어댑터를 만들어보자. 이 어댑터는 호환이 가능한지 체크하고 실제로 작동할 컨트롤러의 기능들이 있는 인터페이스 이다.
public interface MyHandlerAdapter {
boolean supportBoolean(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response,Object handler) throws ServletException, IOException;
}
이 인터페이스를 받을 이번예시는 V3모델을 받는 예시로 만들어보자.
public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean supportBoolean(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 static MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
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;
}
}
해당 ControllerV3HandlerAdapter를 보게 되면 직전에 만든 어댑터 인터페이스를 상속 받았으며 supportBoolean에서 상속이 가능한지 boolean타입으로 확인을 하고 가능하다는 전제가 있다면 handle이라는 메서드에서 Object타입으로 V3컨트롤러를 받아서 ControllerV3타입으로 변환한 뒤 이전에 V3와 같이 기능들이 작동하는 모습이다.
이제 해당 어댑터를 사용할 Front-Controller를 만들어보자.
@WebServlet(name="frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
//private Map<String, ControllerV4> controllerMap = new HashMap<>(); 기존의 방식 하나의 방식만 사용이 가능.
private final Map<String ,Object> handlerMappingMap = new HashMap<>();
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
public FrontControllerServletV5(){
initHandlerMappingMap();
initHandlerAdapter();
}
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 initHandlerAdapter() {
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 myView =viewResolver(viewName);
myView.render(mv.getModel(),request,response);
}
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if (adapter.supportBoolean(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("handler Adapter를 찾지 못햇습니다." + handler);
}
private Object getHandler(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return handlerMappingMap.get(requestURI); // 해당 키 값의 value 추출
}
private static MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
private static Map<String, String> createParaaMap(HttpServletRequest request) {
Map<String ,String > paramMap = new HashMap<>();
request.getParameterNames().asIterator()
.forEachRemaining(paramName->paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
코드가 기니까 천천히 분석해보자. 여태 우리는 Map타입으로 각각의 컨트롤러를 받기 위해
private Map<String, ControllerV3> controllerMap = new HashMap<>();
이런식으로 지정된 Controller타입을 받아왔지만 이제는 어떤 타입이 올지 모르므로 Object타입으로 받아온 모습을 확인 할 수 있다. 그리고 어댑터도 종류별로 갖고 잇을수 있으므로
private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
이렇게 리스트를 준비해 주었다.
이제 이렇게 만든 두 객체를 사용하기 위해 이번엔 V3방식으로 채택했으므로 V3의 컨트롤러 3가지와 직전에 만든 V3핸들러어댑터를 넣었다.
public FrontControllerServletV5(){
initHandlerMappingMap();
initHandlerAdapter();
}
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 initHandlerAdapter() {
handlerAdapters.add(new ControllerV3HandlerAdapter());
}
이제 준비는 끝낫다. 본격적인 service부분 코드를 분석해보자. 먼저
Object handler = getHandler(request);
if(handler == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
이 부분을 보게 되면 아래에 따로 getHandler라는 메서드를 확인하면 알 수 있듯이 현재 요청된 URI의 정보를 가지고
ControllerMappingMap에 키값으 통해 각각의 컨트롤러를 가져온 모습이다. 역시 어떤 객체가 올지 모르므로 Object타입으로 받아왔다. 다음 볼 코드는
MyHandlerAdapter adapter = getHandlerAdapter(handler);
getHandlerAdapter메서드를 확인해보면
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapters) {
if (adapter.supportBoolean(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("handler Adapter를 찾지 못햇습니다." + handler);
}
이렇게 생겼다. 천천히 보면 먼저 각각의 컨트롤러를 Object타입으로 받아온 handler를 가져왔으며 이전에 handlerAdapter에 V3타입으로 만든 어댑터를 추가한게 있다. 그것을 하나씩 꺼내어 adapter라는 변수에 담고 그 값이 이전에 어댑터의 supportBoolean으로 호환이 되는지 확인을 하고 우리는 현재 어댑터에 V3하나만 호환이 되게 만들었고 지금 가져온 handlerAdapter 안에 역시 V3어댑터가 들어있으므로 true가 되어 해당 어댑터를 return한 모습이다.
ModelView mv = adapter.handle(request,response,handler);
해당 컨트롤러가 우리가 만든 어댑터이고 어댑터핸들러에 추가가 되어있다면 해당 컨트롤러를 가지고 마지막으로 컨트롤러 객체를 가지고 어댑터로 이동하여 사용된 뒤 해당 컨트롤러가 작동을 하고 각 컨트롤러에서 가져온 getmodel과 viewName을 통해 MyView객체를 만들고 render메서드를 사용해 디스패처를 만들고 포워딩이 가능하다.
이렇게 한가지 어댑터만 사용한 모습을 보면 이렇게까지 해야하나 싶지만 이제 어댑터가 늘어나면 이렇게 어댑터 핸들러를 사용하는 진가를 알 수 있을것이다.