✔️프론트 컨트롤러(Front Controller) 패턴 특징
✔️프론트 컨트롤러(Front Controller) V1 특징
✔️프론트 컨트롤러(Front Controller) V1 분석
urlPatterns = "/front-controller/v1/*"
: /front-controller/v1
를 포함한 하위 모든 요청을 이 서블릿에서 받아들인다.controllerMap
service()
: requestURI()
메서드로 조회해서 실제 호출할 컨트롤러를 controllerMap
에서 찾는다. 없으면 404를 반환하도록 하고 있다면 process()
메서드를 호출해서 해당 컨트롤러를 실행한다.public interface ControllerV1 {
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
// 다형성 활용
/* String : URL */
/* ControllerV1 : Form/Save/List */
private Map<String, ControllerV1> controllerV1Map = new HashMap<>();
public FrontControllerServletV1() {
controllerV1Map.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());
controllerV1Map.put("/front-controller/v1/members/save", new MemberSaveControllerV1());
controllerV1Map.put("/front-controller/v1/members", new MemberListControllerV1());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("controllerV1Map = " + controllerV1Map);
// Ex. /front-controller/v1/members/new-form
String requestURI = req.getRequestURI();
// 컨트롤러 호출
ControllerV1 controller = controllerV1Map.get(requestURI);
if (controller == null) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
controller.process(req, resp);
}
}
public class MemberFormControllerV1 implements ControllerV1 {
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
public class MemberListControllerV1 implements ControllerV1 {
private final MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
String viewPath = "/WEB-INF/views/members.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
public class MemberSaveControllerV1 implements ControllerV1 {
private final 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);
String viewPath = "/WEB-INF/views/save-result.jsp";
request.setAttribute("member", member);
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
✔️프론트 컨트롤러(Front Controller) V2 특징
public interface ControllerV2 {
MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {
// 다형성 활용
/* String : URL */
/* ControllerV1 : Form/Save/List */
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 req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("controllerV2Map = " + controllerV2Map);
// Ex. /front-controller/v1/members/new-form
String requestURI = req.getRequestURI();
// 컨트롤러 호출
ControllerV2 controller = controllerV2Map.get(requestURI);
if (controller == null) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyView myView = controller.process(req, resp);
myView.render(req, resp);
}
}
public class MemberSaveControllerV2 implements ControllerV2 {
private final 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");
}
}
public class MemberListControllerV2 implements ControllerV2 {
private final MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
return new MyView("/WEB-INF/views/members.jsp");
}
}
public class MemberFormControllerV2 implements ControllerV2 {
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
return new MyView("/WEB-INF/views/new-form.jsp");
}
}
✔️개선할 부분
✔️프론트 컨트롤러(Front Controller) V3 특징
public class ModelView {
// 뷰 논리 이름
private String viewName;
// String : 키
// Object : 데이터
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName) {
this.viewName = viewName;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public Map<String, Object> getModel() {
return model;
}
public void setModel(Map<String, Object> model) {
this.model = model;
}
}
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
// 다형성 활용
/* String : URL */
/* ControllerV1 : Form/Save/List */
private Map<String, ControllerV3> controllerV3Map = new HashMap<>();
public FrontControllerServletV3() {
controllerV3Map.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
controllerV3Map.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
controllerV3Map.put("/front-controller/v3/members", new MemberListControllerV3());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("controllerV3Map = " + controllerV3Map);
/* Ex. /front-controller/v1/members/new-form */
String requestURI = req.getRequestURI();
// 컨트롤러 호출
ControllerV3 controller = controllerV3Map.get(requestURI);
if (controller == null) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> paramMap = createParamMap(req);
ModelView mv = controller.process(paramMap);
String viewName = mv.getViewName();// Ex. 논리 이름 : save-result
// Ex. 물리적 경로 : "/WEB-INF/views/save-result.jsp"
MyView myView = viewResolver(viewName);
myView.render(mv.getModel(), req, resp);
}
private static MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
// createParamMap() : HttpServletRequest에서 파라미터 정보를 꺼내 Map으로 변환한다. 해당 Map을 컨트롤러에 전달하면서 호출
private static Map<String, String> createParamMap(HttpServletRequest req) {
Map<String, String> paramMap = new HashMap<>();
req.getParameterNames().asIterator().forEachRemaining(paramName -> paramMap.put(paramName, req.getParameter(paramName)));
return paramMap;
}
}
public class MemberSaveControllerV3 implements ControllerV3 {
private final 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 modelView = new ModelView("save-result");
modelView.getModel().put("member", member);
return modelView;
}
}
public class MemberListControllerV3 implements ControllerV3 {
private final MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
List<Member> members = memberRepository.findAll();
ModelView modelView = new ModelView("members");
modelView.getModel().put("members", members);
return modelView;
}
}
public class MemberFormControllerV3 implements ControllerV3 {
@Override
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form");
}
}
✔️프론트 컨트롤러(Front Controller) V4 특징
V3와 동일하나 컨트롤러에서 뷰의 논리 이름인 viewName을 반환한다.
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
// 다형성 활용
/* String : URL */
/* ControllerV1 : Form/Save/List */
private Map<String, ControllerV4> controllerV4Map = new HashMap<>();
public FrontControllerServletV4() {
controllerV4Map.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
controllerV4Map.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
controllerV4Map.put("/front-controller/v4/members", new MemberListControllerV4());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("controllerV4Map = " + controllerV4Map);
// Ex. /front-controller/v1/members/new-form
String requestURI = req.getRequestURI();
// 컨트롤러 호출
ControllerV4 controller = controllerV4Map.get(requestURI);
if (controller == null) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, Object> model = new HashMap<>();
Map<String, String> paramMap = createParamMap(req);
String viewName = controller.process(paramMap, model);
// Ex. 물리적 경로 : "/WEB-INF/views/save-result.jsp"
MyView myView = viewResolver(viewName);
myView.render(model, req, resp);
}
private static MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
private static Map<String, String> createParamMap(HttpServletRequest req) {
Map<String, String> paramMap = new HashMap<>();
req.getParameterNames().asIterator().forEachRemaining(paramName -> paramMap.put(paramName, req.getParameter(paramName)));
return paramMap;
}
}
public class MemberSaveControllerV4 implements ControllerV4 {
private final 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";
}
}
public class MemberListControllerV4 implements ControllerV4 {
private final MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
List<Member> members = memberRepository.findAll();
model.put("members", members);
return "members";
}
}
public class MemberFormControllerV3 implements ControllerV3 {
@Override
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form");
}
}
✔️어댑터 패턴(Adapter Pattern)
여태까지 개발한 컨트롤러는 완전히 다른 인터페이스로 호환이 불가능하다. 어댑터 패턴을 사용해서 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 변경해보자.
✔️핸들러 어댑터 : 어댑터 역할을 해주는 덕분에 다양한 종류의 컨트롤러를 호출할 수 있다.
✔️핸들러 : 컨트롤러의 이름을 더 넓은 범위인 핸들러로 변경했다.
supports()
메서드를 통해 어댑터가 해당 컨트롤러를 처리할 수 있는지 판단한다.handle()
메서드를 통해 호출하고 그 결과로 ModelView
를 반환한다.✔️핸들러 어댑터
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws ServletException, IOException;
}
✔️컨트롤러 V3를 지원하는 핸들러 어댑터
public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
// 해당 핸들러를 지원할 수 있는지?
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV3);
}
// [3]. 각 버전의 컨트롤러에서 handle() 메서드를 수행
@Override
public ModelView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws ServletException, IOException {
ControllerV3 controllerV3 = (ControllerV3) handler;
Map<String, String> paramMap = createParamMap(req);
// [4]. 핸들러 호출
ModelView mv = controllerV3.process(paramMap);
// [5]. 핸들러 어댑터에서 모델 뷰 반환
return mv;
}
private Map<String, String> createParamMap(HttpServletRequest req) {
Map<String, String> paramMap = new HashMap<>();
req.getParameterNames().asIterator().forEachRemaining(paramName -> paramMap.put(paramName, req.getParameter(paramName)));
return paramMap;
}
}
✔️컨트롤러 V4를 지원하는 핸들러 어댑터
public class ControllerV4HandlerAdapter implements MyHandlerAdapter {
// 해당 핸들러를 지원할 수 있는지?
@Override
public boolean supports(Object handler) {
return (handler instanceof ControllerV4);
}
// [3]. 각 버전의 컨트롤러에서 handle() 메서드를 수행
@Override
public ModelView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws ServletException, IOException {
ControllerV4 controllerV4 = (ControllerV4) handler;
Map<String, Object> model = new HashMap<>();
Map<String, String> paramMap = createParamMap(req);
// [4]. 핸들러 호출
String viewName = controllerV4.process(paramMap, model);
// [5]. 핸들러 어댑터에서 모델 뷰 반환
ModelView modelView = new ModelView(viewName);
modelView.setModel(model);
return modelView;
}
private Map<String, String> createParamMap(HttpServletRequest req) {
Map<String, String> paramMap = new HashMap<>();
req.getParameterNames().asIterator().forEachRemaining(paramName -> paramMap.put(paramName, req.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> handlerAdapterList = new ArrayList<>();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Object handler = getHandler(req);
if (handler == null) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyHandlerAdapter a = getHandlerAdapter(handler);
ModelView modelView = a.handle(req, resp, handler);
String viewName = modelView.getViewName();
// [6]. viewResolver 호출 + [7]. MyView 반환
MyView myView = viewResolver(viewName);
// [8]. render() 호출
myView.render(modelView.getModel(), req, resp);
}
/* [2]. 핸들러 어댑터 목록에서 해당 핸들러를 처리할 수 있는 핸들러 어댑터를 조회 */
private MyHandlerAdapter getHandlerAdapter(Object handler) {
for (MyHandlerAdapter adapter : handlerAdapterList) {
if (adapter.supports(handler)) {
return adapter;
}
}
throw new IllegalArgumentException("핸들러 어댑터를 찾을 수 없습니다.");
}
/* [1]. 클라이언트로부터 HTTP 요청을 받아 핸들러 매핑 정보에서 핸들러를 조회 */
private Object getHandler(HttpServletRequest req) {
String requestURI = req.getRequestURI();
Object handler = handlerMappingMap.get(requestURI);
return handler;
}
public FrontControllerServletV5() {
initHandlerMappingMap();
initHandlerAdapters();
}
private void initHandlerAdapters() {
handlerAdapterList.add(new ControllerV3HandlerAdapter());
handlerAdapterList.add(new ControllerV4HandlerAdapter());
}
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());
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 static MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}