FrontController
프론트 컨트롤러의 도입함으로써 하나의 입구를 만들어 공통된 기능을 용이하게 처리할 수 있다.
프론트 컨트롤러를 제외한 나머지는 서블릿을 사용하지 않아도 된다.
스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있음.
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
private Map<String, ControllerV1> controllerMap = new HashMap<>();
public FrontControllerServletV1() {
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 {
System.out.println("FrontControllerServletV1.service");
String requestURI = request.getRequestURI();
ControllerV1 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
controller.process(request, response);
}
}
V1 으로써 하나의 Controller 인터페이스를 통해서 각자 (멤버 저장, 멤버 리스트 등등) 필요한 컨트롤러를 구현한다.
후에 FrontController (유일한 servlet) 에서 매핑정보를 조회( HashMap 으로 <Url, 해당되는 객체> ) 하여 공통적으로 구현된 메서드를 호출 해준다.
View의 분리
모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있었다. String viewPath, foward 등등.
@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {
private Map<String, ControllerV2> controllerMap = new HashMap<>();
public FrontControllerServletV2() {
controllerMap.put("/front-controller/v2/members/new-form", new MemberFormControllerV2());
controllerMap.put("/front-controller/v2/members/save", new MemberSaveControllerV2());
controllerMap.put("/front-controller/v2/members", new MemberListControllerV2());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV2 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyView view = controller.process(request, response);
view.render(request, response);
}
}
Controller 단에서 MyView("/WEB-INF/views/new-form.jsp") 처럼 객채를 생성해서 return 해준다.
MyView 단에서 render 메소드 안에서 viewPath == 위의주소 를 받아오고 그곳으로 forward를 호출해줌.
중복되는 코드를 지울 수 있게 되었다.
Model 추가
@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 MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
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;
}
}
서블릿 종속성 제거
요청 파라미터 정보는 자바의 Map으로 대신 넘기도록 하면 지금 구조에서는 컨트롤러가 서블릿 기술이 필요가 없다.
따라서 request 객체를 별도의 Model 객체를 만들어서 반환하면 된다. 지금은 ModelView로 논리이름 + 매핑된정보Model을 객체로 만들어 DTO 개념으로 전달한다.
뷰이름 중복제거
컨트롤러는 뷰의 논리 이름("new-form" 만) 을 반환하고 실제 물리위치의(WEB-INF~등등) 이름은 프론트 컨트롤러에서 처리하도록 단순화하자. 논리이름을 받은후에 viewResolver를 통해 물리뷰 경로를 배출한다.
forward 전에 controller와 viewresolver를 통해 받은 model 을 request.setAttribute()를 통해 저장한다. 후에 view 와 함꼐 JSP로 포워드 해서 JSP를 렌더링 해준다.
단순하고 실용적인 컨트롤러
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);
}
기본적인 구조는 V3 과 같다. 대신에 컨트롤러가 ModelView를 반환하지않고 ViewName만 반환한다.
모델을 각 controller에서 만드는것이 아니라 FrontController에서 <String, Object> model을 만들어서 넘겨주면 각각의 Controller에서 저 model 에 put을 이용하여 요구되어진 정보들을 넣어준다.
render(model,request,response) 를 해주면 메소드 내부에서 forward 해준다.
유연한 컨트롤러 (V1~V5 등등 다양한 컨트롤러를 쓰고싶음)
현재 Controller V1,~V5 는 서로 완전히 다른 인터페이스이다. 호환이 불가능하다.
@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();
}
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());
//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());
}
@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 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) {
//MemberFormControllerV4
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");
}
}
어댑터 패턴 = 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 변경함
핸들러 어댑터 = 여기서 어댑터 역할을 해주는 덕분에 다양한 종류의 컨트롤러를 호출 할 수 있다.
핸들러 = 컨트롤러의 이름의 더 넓은 범위의 이름.
requestUri를 보고 handler(Controller) 를 찾아옴 -> 이 handler를 handle할수있는 어댑터 조회 -> 어댑터가 실제 handler 호출함
SpringMVC도 핸들러 어댑터들을 통해서 @이 붙은 많은 Controller 다 쓸 수 있다. 전체적인 틀을 건드리지 않고
MyHandlerAdapter 라는 interface 는 supports(Handler) == 이 핸들러어뎁터가 해당 핸들러를 처리가능한건지 여부 , handle(request,response , Handler) 위의 support 를 통해 한번 거르기 때문에 해당 핸들러를 캐스팅해서 실제로 사용한다.
request url 을 통해서 Handler를 받아오면 이게 어떤 HandlerAdapter를 통해서 구동이 가능한지를 HandlerAdapter 목록에서 찾아본다.
그 HandlerAdapter를 통해서 받은 Handler를 구동시킨다.
기능즉 새로운 V5 ,V6 가 계속 생겨도 메인로직(FrontController) 을 거의 건드리지 않고 Adapter들만 만들어서 넣어주기만 하면 잘 동작하는것을 확인할 수 있다.
'WEB > Spring MVC 1' 카테고리의 다른 글
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(스프링 MVC - 기본기능) (0) | 2021.03.30 |
---|---|
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(스프링 MVC - 구조이해) (0) | 2021.03.29 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(서블릿,JSP,MVC패턴) (0) | 2021.03.28 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(서블릿) (0) | 2021.03.27 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(웹 애플리케이션 이해) (0) | 2021.03.27 |