사실 스프링 mvc의 핵심이라고 볼 수 있다.
이 front-controller가 바로 "공통의 관심사"를 모아서 처리해주기 때문이다.
그래서 이 front-controller가 바로 spring mvc의 dispatcherServlet이다.
그럼 공통처리도 가능해지고 굉장히 많던 서블릿도 하나로 줄일 수 있다.
public interface ControllerV1 {
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
private Map<String, ControllerV1> controllerV1Map = new HashMap<>();
public FrontControllerServletV1() {
controllerV1Map.put("/front-controller/v1/members/new-form", new MemberControllerV1());
controllerV1Map.put("/front-controller/v1/members/save", new MemberSaveControllerV1());
controllerV1Map.put("/front-controller/v1/members", new MemberListControllerV1());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV1 controller = controllerV1Map.get(requestURI);
System.out.println("controller = " + controller);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
controller.process(request, response);
}
}
public class MemberSaveControllerV1 implements ControllerV1 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
request.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
public class MyView {
private String viewPath;
public MyView(String viewPath) {
this.viewPath = viewPath;
}
public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {
private Map<String, ControllerV2> controllerV2Map = new HashMap<>();
public FrontControllerServletV2() {
controllerV2Map.put("/front-controller/v2/members/new-form", new MemberFormControllerV2());
controllerV2Map.put("/front-controller/v2/members/save", new MemberSaveControllerV2());
controllerV2Map.put("/front-controller/v2/members", new MemberListControllerV2());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV2 controller = controllerV2Map.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyView view = controller.process(request, response);
view.render(request, response);
}
}
public class MemberSaveControllerV2 implements ControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
request.setAttribute("member", member);
return new MyView("/WEB-INF/views/save-result.jsp");
}
}
@Getter
@Setter
public class ModelView {
private String viewName;
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName) {
this.viewName = viewName;
}
}
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
public class MemberSaveControllerV3 implements ControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member);
return mv;
}
}
@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
private Map<String, ControllerV3> controllerMap = new HashMap<>();
public FrontControllerServletV3() {
controllerMap.put("/front-controller/v3/members/new-form", new
MemberFormControllerV3());
controllerMap.put("/front-controller/v3/members/save", new
MemberSaveControllerV3());
controllerMap.put("/front-controller/v3/members", new
MemberListControllerV3());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV3 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
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");
}
}
opt + cmd + M
, ctrl + alt + m
: 드래그 영역 메서드로 빼기
왜? -> 준위를 맞춰줘야하기 때문에.
논리 이름을 물리 이름으로 바꿔주는게 즉, 실제 view를 찾아주는 역할을 하는게 view Resolver다.
사실 위 개선방법 3이 너무 많이 바뀌어서 헷갈릴 수 있다. 그리고 ModelView가 추가되어 복잡할 수 있다. 이를 빼보자.
개념은 바로 빈모델을 파라미터로 넘겨서 컨트롤러에서 저장하도록 하고 컨트롤러는 해당 파라미터로 비즈니스 로직을 실행 후 뷰의 논리이름을 반환하도록 하는 것이다.
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
public class MemberSaveControllerV4 implements ControllerV4 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model){
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
model.put("member", member);
return "save-result";
}
}
@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
private Map<String, ControllerV4> controllerMap = new HashMap<>();
public FrontControllerServletV4() {
controllerMap.put("/front-controller/v4/members/new-form", new
MemberFormControllerV4());
controllerMap.put("/front-controller/v4/members/save", new
MemberSaveControllerV4());
controllerMap.put("/front-controller/v4/members", new
MemberListControllerV4());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV4 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> paramMap = createParamMap(request);
Map<String, Object> model = new HashMap<>(); //추가
String viewName = controller.process(paramMap, model);
MyView view = viewResolver(viewName);
view.render(model, 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");
}
}
개선방법 4의 FrontController를 보면 가장 중요한 역할을 하는객체인 controllerMap이 ControllerV4를 명시해서 받는다. 이렇게 되면 다른 인터페이스로 개발을 할 수 없다.
이를 개선하기 위해 어댑터
개념을 도입하고
이 어댑터를 통하여 컨트롤러를 호출한다. 다만, 여기서 컨트롤러에서 핸들러로 이름을 바꾼 이유는 해당 종류의 "어댑터"만 있다면 어떠한 데이터도 다 처리할 수 있기 때문에 "컨트롤러"에 국한되지 않아서 이름을 바꿨다.
즉, 프론트 컨트롤러에 더 많은 기능을 추가하자는 것이다.
유연한 컨트롤러를 만들어보자는 것이다.
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
support 메서드에서 handler는 컨트롤러를 뜻한다.
어댑터가 해당 컨트롤러를 처리할 수 있는지 판단하는 메서드이다.
handle메서드는 실제 컨트롤러를 호출하고 해당 ModelView를 반환한다.
만약 반환하지 못 하면 어댑터가 ModelView를 직접 생성해서라도 반환해야한다.
이전에는 프론트 컨트롤러가 실제 컨트롤러를 호출했지만 이제는 이 어댑터를 통해서 실제 컨트롤러가 호출된다.
이제 얘를 구현한 실제 구현제가 각각의 Controller의 버전에 맞춰서 파라미터를 생성한 ViewModel을 만들어서 논리이름과 함께 mv이라는 변수로 반환할 것이다.
public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean supports(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 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 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 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;
}
}
@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();
}
public 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());
// 추가 확장하고자 한다면 여기만 추가
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());
}
@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 handlerAdapter = getHandlerAdapter(handler);
ModelView mv = handlerAdapter.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 MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}