프론트 컨트롤러 패턴 소개
공통의 관심사를 별도로 모으는 소위 문지기 역할을 하는 것을 controller앞에 도입한다.
프론트 컨트롤러 패턴 특징
- 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
- 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
- 입구를 하나로
- 공통 처리 가능
- 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨.
스프링 웹 MVC와 프론트 컨트롤러
스프링 웹MVC의 핵심도 바로 프론트컨트롤러이며 스프링 웹MVC의 DispatcherServlet이 프론트 컨트롤러 패턴으로 구현되어 있음
프론트 컨트롤러 도입 전 과 후의 아키텍쳐
Goal : 기존 코드를 최대한 유지하면서, 프론트 컨트롤러를 도입해본다. 먼저 구조를 맞추어두고 점진적으로 리팩토링을 한다.
프론트 컨트롤러 도입 v1
v1 구조 아키텍쳐
Controller V1
hello/servlet/web/frontcontroller/v1/ControllerV1.java
package hello.servlet.web.frontcontroller.v1;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface ControllerV1 {
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
- 서블릿과 비슷한 모양의 컨트롤러 인터페이스 도입
- 각 컨트롤러는 이 인터페이스를 구현하면 됨
- 프론트 컨트롤러는 이 인터페이스를 호출하여 구현과 관계없이 로직의 일관성을 가져갈 수 있음
MemberFormControllerV1 - 회원 등록 컨트롤러
hello/servlet/web/frontcontroller/v1/controller/MemberFormControllerV1.java
package hello.servlet.web.frontcontroller.v1.controller;
import hello.servlet.web.frontcontroller.v1.ControllerV1;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
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);
}
}
MemberSaveControllerV1 - 회원 저장 컨트롤러
hello/servlet/web/frontcontroller/v1/controller/MemberSaveControllerV1.java
package hello.servlet.web.frontcontroller.v1.controller;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v1.ControllerV1;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
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")); // request의 getParameter 결과는 항상 문자! 그러므로 타입을 변환해 주어야 한다
Member member = new Member(username,age);
memberRepository.save(member);
//Model에 데이터를 보관한다.
request.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
MemberListControllerV1 - 회원 목록 조회 컨트롤러
hello/servlet/web/frontcontroller/v1/controller/MemberListControllerV1.java
package hello.servlet.web.frontcontroller.v1.controller;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v1.ControllerV1;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
public class MemberListControllerV1 implements ControllerV1 {
private 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);
}
}
- 내부 로직은 기존 서블릿과 동일하다.
- 뷰도 동일하게 사용한다.
FrontController - 프론트 컨트롤러
hello/servlet/web/frontController/v1/FrontControllerServletV1.java
package hello.servlet.web.frontcontroller.v1;
import hello.servlet.web.frontcontroller.v1.controller.MemberFormControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberListControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberSaveControllerV1;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name="frontControllerServletV1", urlPatterns = "/front-controller/v1/*") //v1 하위의 어떤 url이든 이 서블릿이 호출됨
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 = new MemberFormControllerV1();
ControllerV1 controller = controllerMap.get(requestURI);
if(controller == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
//다형성으로 인해 override된 메서드가 호출이 된다
controller.process(request,response);
}
}
- urlPatterns
- urlPatterns="/front-controller/v1/*" : /front-controller/v1 를 포함한 하위 모든 요청은 이 서블릿에서 받아들인다
- controllerMap
- key:매핑 URL
- value: 호출될 컨트롤러
- service()
- 먼저 requestURL를 조회해서 실제 호출할 컨트롤러를 controllerMap에서 찾고, 없다면 404를 반환한다.
- 컨트롤러를 찾고 controller.process(request,response);을 호출해서 해당 컨트롤러를 실행한다
- JSP
- JSP는 이전 MVC에서 사용했던 것을 재사용한다.
http://localhost:8080/front-controller/v1/members/new-form 등록 실행 결과
http://localhost:8080/front-controller/v1/members 목록 조회 실행 결과
http://localhost:8080/front-controller/v1/aa 404
코드의 의미는 알겠지만 oop의 특징인 다형성을 더욱 이해해야겠다는 생각이 든다! 하면 할 수록 할 건 더 많아지는데, 빨리 더 알고 싶다. 그러나 지치지 않도록, 꾸준히 근성으로!
@ 스프링 MVC 1편 - 백엔드 웹개발 by 김영한을 참조하고 있습니다.
'Spring > mvc' 카테고리의 다른 글
[Spring MVC 1] - MVC 프레임워크 - Model 추가 (0) | 2022.08.29 |
---|---|
[Spring MVC 1] - MVC 프레임워크 - view 분리 (0) | 2022.08.16 |
[Spring MVC 1] - MVC 패턴 (0) | 2022.08.12 |
[Spring MVC 1] - 회원 관리 웹 어플리케이션 JSP로 구현 (0) | 2022.08.12 |
[Spring MVC 1] - 회원 관리 웹 어플리케이션 Servlet으로 구현 (0) | 2022.08.11 |