Front Controller Pattern
모든 요청을 하나의 컨트롤러가 받아서 처리하는 디자인 패턴이다. Spring MVC의 DispatcherServlet이 바로 이 패턴을 사용한다.
패턴의 탄생 배경
초기 웹 개발에서는 CGI(Common Gateway Interface) 방식으로 각 요청마다 새로운 프로세스를 생성했다. Servlet은 이를 개선했지만, 여전히 요청마다 별도의 Servlet이 필요했다. Front Controller 패턴은 이러한 문제를 해결하기 위해 등장했다.
기존 서블릿 작성 방식의 문제점
- 사용자의 요청과 이를 처리하는 Servlet이 1:1 매칭됨
- 요청이 추가될 때마다 매번 새로운 Servlet 생성 필요
- 동일한 루틴으로 서블릿 생성 필요 (상속, 메서드 재정의 등)
- 개별 서블릿에서
- 비즈니스 로직과 함께 로깅, 예외 처리, 응답 처리 방식 등을 함께 처리: 코드의 중복 발생
- 서블릿마다 요청 처리 과정이 흩어져 있어 복잡성이 증가하고 유지보수가 어려워짐
Front Controller Pattern이란?
Front Controller = 전면에서 모든 요청을 받아들이는 Servlet
장점
- 단일 진입점: 모든 요청을 front controller에서 접수하므로 요청 처리의 일관성
- 공통 처리: 모든 작업이 front controller를 거쳐감
- 필요한 전/후 작업 (인증, 권한 검사, 로깅 등)의 일괄 처리 가능
- 유연한 확장성: 새로운 요청 처리를 추가할 때 기존의 구조를 크게 변경하지 않고도 쉽게 확장 가능
- 코드 간결성: 여러 개의 Servlet을 만드는 번거로움이 줄고 코드의 가독성도 향상
기본 Front Controller 구현
HashMap으로 URL과 Handler를 매핑하여 관리한다.
FrontController.java
@WebServlet("/app/*") // /app/* 로 들어오는 모든 요청 처리
public class FrontController extends HttpServlet {
private Map<String, Handler> handlerMap;
@Override
public void init() throws ServletException {
handlerMap = new HashMap<>();
// URL과 Handler 매핑 - HashMap으로 등록
handlerMap.put("/app/home", new HomeHandler());
handlerMap.put("/app/login", new LoginHandler());
handlerMap.put("/app/board/list", new BoardListHandler());
// 추가 매핑...
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uri = request.getRequestURI();
String mappingKey = uri.substring(request.getContextPath().length()); // 컨텍스트 경로 제거
try {
// 1. 공통 전처리 (인코딩, 로깅 등)
request.setCharacterEncoding("UTF-8");
// 응답 인코딩 설정
response.setContentType("text/html; charset=UTF-8");
// 2. Handler 찾기
Handler handler = handlerMap.get(mappingKey);
if (handler == null) {
response.sendError(404, "페이지를 찾을 수 없습니다");
return;
}
// 3. Handler 실행
String viewName = handler.handle(request, response);
// 4. View 처리
if (viewName != null) {
if (viewName.startsWith("redirect:")) {
// 리다이렉트
String redirectUrl = viewName.substring(9);
response.sendRedirect(request.getContextPath() + redirectUrl);
} else {
// JSP 포워드
String viewPath = "/WEB-INF/views/" + viewName + ".jsp";
request.getRequestDispatcher(viewPath).forward(request, response);
}
}
} catch (Exception e) {
e.printStackTrace();
response.sendError(500, "시스템 오류가 발생했습니다");
}
}
}인코딩 처리 상세 설명
POST vs GET 인코딩 차이점
| 구분 | POST 요청 | GET 요청 |
|---|---|---|
| 한글 데이터 위치 | HTTP Body (form-data) | URL Query String |
| 인코딩 설정 방법 | request.setCharacterEncoding("UTF-8") | WAS 설정 파일 (server.xml) |
| 설정 시점 | getParameter() 호출 전 | WAS 시작 시 |
| 예시 | <form method="post"> | ?name=홍길동&age=20 |
Tomcat server.xml 설정 예시
<!-- GET 요청의 한글 처리를 위한 설정 -->
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8" /> <!-- GET 쿼리스트링 인코딩 -->중요:
request.getRequestURI()는 컨텍스트 경로(예:/myapp)를 포함하므로, 위 코드처럼request.getContextPath()길이만큼 잘라낸 값을 매핑 키로 사용해야 정확한 URL 패턴 매칭이 가능하다.
Handler 인터페이스
Handler.java
// Handler 인터페이스
public interface Handler {
String handle(HttpServletRequest request, HttpServletResponse response) throws Exception;
}LoginHandler.java
// Handler 구현 예시
public class LoginHandler implements Handler {
@Override
public String handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
String method = request.getMethod();
if ("GET".equals(method)) {
// 로그인 폼 표시
return "user/login";
} else if ("POST".equals(method)) {
// 로그인 처리 로직
// 1. 파라미터 받기
// 2. 인증 처리
// 3. 세션 설정
// 4. 적절한 뷰 반환
return "redirect:/app/home";
}
return null;
}
}요청의 구분과 URL 매핑
WAS에서 들어온 요청을 어떻게 구분하고 처리할지 결정하는 것이 URL 매핑이다.
URL 매핑 전략
1. 쿼리 파라미터로 작업 지정
URL에 특정 작업을 의미하는 파라미터(예: action)를 추가하여 하나의 Servlet에서 여러 기능 처리
// /main?action=gugu&dan=3
// /main?action=hello
// /main?action=add&num1=10&num2=20
@WebServlet("/main")
public class MainServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
String action = request.getParameter("action");
switch(action) {
case "gugu":
int dan = Integer.parseInt(request.getParameter("dan"));
// 구구단 처리
break;
case "hello":
// 인사 처리
break;
case "add":
int num1 = Integer.parseInt(request.getParameter("num1"));
int num2 = Integer.parseInt(request.getParameter("num2"));
// 덧셈 처리
break;
}
}
}2. 와일드카드를 이용한 URL 매핑
| URL 패턴 | 특징 | 예시 | 경로 파악 방법 |
|---|---|---|---|
/main/* | 도메인(기능) 단위로 하위 모든 경로 수용 | /main/hello/main/gugu?dan=3 | getServletPath(): /maingetRequestURI(): 전체 경로 |
*.do | 확장자 기반 매핑 경로와 무관하게 .do로 끝나는 모든 요청 처리 | /hello.do/board/list.do | getServletPath(): 빈 문자열getRequestURI(): 전체 경로 |
/ | 매핑되지 않은 모든 요청의 폴백 (Default Servlet) | 정적 리소스 처리 Spring MVC 기본 매핑 | 모든 요청 처리 |
와일드카드 매핑 예제
// 도메인 단위 매핑
@WebServlet("/board/*")
public class BoardServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
String uri = request.getRequestURI(); // /contextPath/board/list
String contextPath = request.getContextPath(); // /contextPath
String command = uri.substring(contextPath.length()); // /board/list
if (command.equals("/board/list")) {
// 게시글 목록 처리
} else if (command.equals("/board/write")) {
// 게시글 작성 처리
} else if (command.equals("/board/view")) {
// 게시글 조회 처리
}
}
}// 확장자 기반 매핑
@WebServlet("*.do")
public class ControllerServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
String uri = request.getRequestURI(); // /contextPath/board/list.do
String contextPath = request.getContextPath(); // /contextPath
String command = uri.substring(contextPath.length()); // /board/list.do
if (command.equals("/board/list.do")) {
// 게시글 목록 처리
} else if (command.equals("/member/login.do")) {
// 로그인 처리
}
}
}URL 매핑 우선순위
Servlet Container는 다음 순서로 URL 패턴을 매칭한다:
- 정확한 매칭:
/board/list(exact match) - 경로 매칭:
/board/*(longest path match) - 확장자 매칭:
*.do - 기본 서블릿:
/
도메인별 Front Controller
Front Controller라고 앞에서 모든 것을 처리하지 않음. 도메인별로 가져갈 수 있다.
- Main Front Controller (/main/*, 일반 요청 처리)
- Member Front Controller (/member/*, 멤버 관련 요청 처리)
- Board Front Controller (/board/*, 게시판 관련 요청 처리)
MVC 패턴과의 관계
Front Controller는 MVC 패턴의 Controller 부분을 더 체계화한 것이다.
- Model: 비즈니스 로직과 데이터
- View: JSP, HTML 등 화면 처리
- Controller: Front Controller + Handler들
정리
- Front Controller 패턴은 모든 요청을 하나의 Servlet이 받아 처리
- HashMap으로 URL과 Handler를 매핑하여 관리
- 공통 로직을 한 곳에서 처리할 수 있어 유지보수가 용이
- Spring MVC의 DispatcherServlet이 이 패턴을 사용
Last updated on