cf. 스프링 웹 MVC의 DispatcherServlet이 프론트 컨트롤러 패턴으로 구현됨
public FrontControllerServletV1() {
// key: 매핑 URL, value: 호출될 컨트롤러
controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());
controllerMap.put("/front-controller/v1/members/save", new MemberSaveControllerV1());
controllerMap.put("/front-controller/v1/members", new MemberListControllerV1());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// ControllerMap에서 requestURI 찾은 뒤 컨트롤러 실행
String requestURI = request.getRequestURI();
ControllerV1 controller = controllerMap.get(requestURI);
// 없는 경로면 404
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
controller.process(request, response);
}
public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 전달받은 URI 경로로 Dispatcher 서블릿을 호출하는 메소드
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
// 각 컨트롤러에서 ModelView 객체 생성
// 논리 뷰 이름과 객체 정보 등을 모델에 담아서 전달
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member);
@Override
protected void service(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
(컨트롤러 조회- v1,v2 동일)
// HttpServletRequest의 파라미터 정보를 map으로 변환
// 해당 컨트롤러 호출
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
// 논리 뷰 이름 가져오기
String viewName = mv.getViewName();
// 물리 뷰로 반환하기(중복 경로 단순화)
MyView view = viewResolver(viewName);
// 뷰 객체를 통해 HTML화면 렌더링
view.render(mv.getModel(), request, response);
}
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;
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 모델의 데이터를 꺼내서 setAttribute로 담아두기
modelToRequestAttribute(model, request);
// 해당 경로로 이동
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
model.forEach((key, value) -> request.setAttribute(key, value));
}
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
(컨트롤러 로직)
...
// 전달받은 모델 객체(map)에 데이터를 담고, 논리 뷰 이름만 리턴
model.put("member", member);
return "save-result";
}
(v3와 동일)
...
// 프론트 컨트롤러에서 모델 객체(map) 생성하여 각 컨트롤러에 전달
Map<String, Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model);
MyView view = viewResolver(viewName);
view.render(model, request, response);
// 사용할 컨트롤러마다 각각 생성
public class ControllerHandlerAdapter implements HandlerAdapter {
// 해당 핸들러(=컨트롤러)를 사용할 수 있는지 판단하는 메서드
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV*);
}
@Override
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
// 핸들러를 컨트롤러로 캐스팅
ControllerV* controller = (ControllerV*)handler;
Map<String, String> paramMap = createParamMap(request);
// 어댑터를 통해 컨트롤러 호출 후 ModelView 리턴
// v3
ModelView mv = controller.process(paramMap);
// v4
// 뷰 이름을 반환하는 v4에서는 어댑터가 직접 ModelView 객체 생성하여 타입에 맞게 리턴해줌
HashMap<String, Object> model = new HashMap<>();
String viewName = controller.process(paramMap, model);
ModelView mv = new ModelView(viewName);
mv.setModel(model);
return mv;
}
}
// 어댑터로 호환할 수 있는 모든 컨트롤러 매핑 경로와 어댑터를 생성자로 초기화(등록)
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/v*/members/new-form", new MemberFormControllerV*());
...
}
private void initHandlerAdapters() {
handlerAdapters.add(new ControllerV*HandlerAdapter());
...
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 핸들러(컨트롤러) 조회, 없으면 404
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);
// 뷰 실행
MyView view = viewResolver(mv.getViewName());
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);
}
[출처] 스프링 MVC 1 - 김영한, 인프런