목차
- 프론트 컨트롤러 패턴 소개
- 프론트 컨트롤러 도입
- view 분리
- model 추가
- 단순하고 실용적인 컨트롤러
- 유연한 컨트롤러
학습 목표 : Spring MVC의 동작 원리를 직접 구현하면서 체득
- 단계적으로 MVC 프론트 컨트롤러를 구현하다 보면, Spring MVC의 DispatcherServlet 구조와 거의 유사한 형태로 귀결됨


DispatcherServlet이 구현하고 있고, 실제로는 이 서블릿이 모든 HTTP 요청의 입구 역할을 수행함[요청]
→ [DispatcherServlet (Front Controller)]
→ [HandlerMapping (요청-컨트롤러 매핑)]
→ [핸들러 어댑터 (호출 방식 맞춤)]
→ [Controller (핸들러)]
→ [ModelAndView 반환]
→ [ViewResolver]
→ [View 렌더링]
→ [응답 반환]
@Controller, @HttpRequestHandler 등 다양한 방식 지원)@Controller, @RestController 클래스가 여기에 해당)"home" -> "/WEB-INF/views/home.jsp")1. 사용자가 GET /members 요청 2. 서블릿 컨테이너가 DispatcherServlet에 요청 전달 3. DispatcherServlet이 HandlerMapping에게 물어봄 → MemberController 매핑 확인 4. HandlerAdapter가 해당 컨트롤러를 호출 5. MemberController가 ModelAndView("members", model) 반환 6. ViewResolver가 "members" → /WEB-INF/views/members.jsp 해석 7. View가 렌더링되어 HTML 반환

GET /members/new-form HTTP/1.1
Front Controller라는 서블릿이 받음ContrllerV1 인터페이스 타입으로 호출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;
}
서블릿과 비슷한 모양의 컨트롤러 인터페이스를 도입
프론트 컨트롤러는 이 인터페이스를 호출해서 구현과 관계없이 로직의 일관성을 가져갈 수 있음
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);
}
}
/front-controller/v1/members/new-form 같은 URL로 들어왔을 때 회원 등록 폼 JSP를 보여주는 역할을 수행함implements ControllerV1 : ControllerV1 인터페이스 구현 -> 프론트 컨트롤러가 호출 가능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"));
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);
}
}
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); // JSP에서 사용할 모델 등록
String viewPath = "/WEB-INF/views/members.jsp"; // 뷰 경로 지정
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response); // JSP 포워딩
}
}
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/*")
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(); // 요청 URI 추출
ControllerV1 controller = controllerMap.get(requestURI); // 매핑 정보 조회
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND); // 404 반환
return;
}
controller.process(request, response); // 컨트롤러 위임 호출
}
}
/front-controller/v1/* -> 이 서블릿이 모든 요청 수신
urlPatterns = "/front-controller/v1/*" : /front-controller/v1 를 포함한 하위 모든 요청은 이 서블릿에서 받아들임
/front-controller/v1 , /front-controller/v1/a , /front-controller/v1/a/b request.getRequestURI()로 현재 경로 파악controllerMaprequestURI를 조회해서 실제 조회할 컨트롤러를 controllerMap에서 찾음404(SC_NOT_FOUND) 상태 코드를 반환함controller.process(request, response)을 호출해서 해당 컨트롤러를 실행함