스프링 MVC - 1편 - MVC 프레임워크 만들기 - 프론트 컨트롤러 패턴(1)
인프런 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술편을 학습하고 정리한 내용 입니다.
프론트 컨트롤러 패턴 소개


FrontController 패턴 특징
- 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
- 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
- 입구를 하나로!
- 공통 처리 가능
- 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨.
스프링 웹 MVC와 프론트 컨트롤러 스프링 웹 MVC의 핵심도 바로 FrontController 스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있음.
프론트 컨트롤러 도입 - V1
프론트 컨트롤러를 단계적으로 도입해 보자. 이번 목표는 기존 코드를 최대한 유지하면서, 프론트 컨트롤러를 도입하는 것이다. 먼저 구조를 맞추어두고 점진적으로 리펙터링 해보자.

컨트롤러 인터페이스를 하나 만들자.

다음 위치에 만들었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
package hello.servlet.web.frontcontroller.v1;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface ControllerV1 {
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
서블릿과 비슷한 모양의 컨트롤러 인터페이스를 도입한다. 각 컨트롤러들은 이 인터페이스를 구현하면 된다.
프론트 컨 트롤러는 이 인터페이스를 호출해서 구현과 관계없이 로직의 일관성을 가져갈 수 있다.
이제 이 인터페이스를 구현한 컨트롤러를 만들어보자. 지금 단계에서는 기존 로직을 최대한 유지 하는게 핵심이다
자 이제 멤버 저장 폼, 멤버 저장, 멤버 리스트 컨트롤러를 만들 거다.

다음과 같이 만들 것이고, 로직 자체는 MVC 패턴 적용 지난번에 했던 MVC 서블릿과 같다. 코드를 보자.
hello.servlet.web.frontcontroller.v1.controller.MemberFormControllerV1
1
2
3
4
5
6
7
8
9
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);
}
}
implements ControllerV1 우리가 만든 인터페이스를 상속 시켰고, 그 안에 process 메서드를 구현하는데, 구현 자체는 이전 시간에 한 코드와 동일하다.
hello.servlet.web.frontcontroller.v1.controller.MemberListControllerV1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}
}
hello.servlet.web.frontcontroller.v1.controller.MemberSaveControllerV1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
// Model에 데이터를 보관한다.
request.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
이렇게 3개의 컨트롤러를 만들었고 이제 프론트 컨트롤러만 남았다.
프론트 컨트롤러는 다음 위치에 만들 거다.

v1 에 만들면 된다.
자 여기 서는 이전에 한 것처럼 서블릿을 만들어야 한다.
hello.servlet.web.frontcontroller.v1.FrontControllerServletV1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@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);
}
}
자 하나하나 보자.
먼저
1
2
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet { ... }
클래스 선언부 에서는 extends HttpServlet 상속 받고
urlPatterns = "/front-controller/v1/*"
url 패턴이 다음과 같다.
이는 즉 /front-controller/v1/ 이 뒤에 뭐가 들어오던 일단 이 컨트롤러가 처리 하겠다는 것이다.
1
2
3
4
5
6
7
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());
}
한 줄 한 줄 보자면, 먼저 Map<String, ControllerV1> 자료구조를 만들어 놨다.
그리고 생성자를 선언 해 놨는데,
서버가 돌아갈 때 서블릿이 생성자를 호출 하기 때문에 이를 이용했다.
잘 보면
1
2
3
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());
키가 URL 패턴이고, 값이 우리가 만들어 놓은 컨트롤러가 들어가 있는 걸 볼 수 있다.
이제 service 메서드를 잘 구현해서 각각 컨트롤러로 보내주면 될 거 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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);
}
service 메서드를 보면 먼저 String requestURI = request.getRequestURI(); 이 코드로 어떤 url이 들어왔는지 파악을 한다.
예를 들어 http://localhost:8080/front-controller/v1/members/new-form 이런 식으로 들어오면
String requestURI = /front-controller/v1/members/new-form 가 된다.
그 다음에 먼저 만들어 놓은 자료구조의 키 값으로 저걸 사용해서
ControllerV1 controller = controllerMap.get(requestURI); 다음과 같이 컨트롤러 객체를 반환 받는다.
우리가 인터페이스로 ControllerV1 을 만들어 놨기 때문에, 상속 받은 자식 클래스들은 다 담을 수 있다.
1
2
3
4
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
혹시 컨트롤러가 없을 수도 있어서 404 보내주기 위한 처리도 해줬다.
마지막으로
1
controller.process(request, response);
컨트롤러의 메서드를 실행 시켜주면 끝난다.
자 이제 보자.

폼 잘뜬다.

저장도 잘 된다.

리스트도 잘 나온다.
view는 이전에 쓰던 걸 재활용 했다.

없는 url을 입력하면 404가 발생하게 된다.
이로써

이 구조를 만들어 봤다.
댓글남기기