[Spring] 서블릿과 JSP로 MVC 흉내내기 , 그리고 한계점
서블릿과 JSP를 통해 MVC를 구현 해볼 예정이다. 구현은 다음과 같다.서블릿 : 컨트롤러JSP : 뷰HttpServletRequest 가 제공하는 내부 저장소 (Attribute) : 모델/WEB-INF 경로참고 사항으로, /WEB-INF 라는 폴더를
bdisappointed.tistory.com
해당 글 에서 만들었던 서블릿 + JSP 로 MVC 구현하기를 프론트 컨트롤러 패턴을 적용하여 리팩토링 해보겠다.
FrontController 패턴 개요
FrontController 패턴의 개요는 다음 포스팅을 참고하자
[Spring] Front Controller 패턴 - 개요
Front Controller 패턴프론트 컨트롤러 패턴은 말 그대로 프론트에서 컨트롤한다는 의미이다.위 사진처럼 컨트롤러에서 공통되는 로직들을 먼저 1차로 Front Controller로 처리 한뒤, 나머지 로직들을 해
bdisappointed.tistory.com
프론트 컨트롤러 도입 - V1
V1은 다음과 같이 설계를 목표로 한다.
서블릿의 형태와 유사한 인터페이스를 구현하여 컨트롤러들이 이 인터페이스를 구현하도록 한다.
즉, 각 컨트롤러는 이 인터페이스를 통해 구현과 관계 없이 로직의 일관성을 가져갈 수 있게 된다.
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 {
// 서블릿의 service 메서드와 같다
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
컨트롤러 구현
회원 등록
package hello.servlet.web.frontcontroller.v1.controller;
import hello.servlet.web.frontcontroller.v1.ControllerV1;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.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);
}
}
회원 저장
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 jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.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);
// Model에 데이터 보관
request.setAttribute("member", member);
// model 정보 기반 view로 던지기
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 jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.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);
}
}
V1 컨트롤러들은 기존 서블릿과 거의 같다.
차이점은 더이상 http서블릿 인터페이스를 상속받지 않기 때문에 외부에서 해당 컨트롤러의 process 메서드를 호출 해야한다.
프론트 컨트롤러
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 jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static jakarta.servlet.http.HttpServletResponse.*;
// 어떤 요청이 들어와도, 예를 들어 /front-controller/v1/xxxx 가 들어오면 무조건 이 프론트 컨트롤러가 실행
@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(SC_NOT_FOUND);
return;
}
controller.process(request,response);
}
}
먼저 url매핑에 와일드카드 기호를 사용하여 /front-controller/v1/xxxx 으로 들어오는 모든 요청을 이 서블릿으로 받도록 설정했다.
이후 Map에 각 컨트롤러를 url을 key로 하여 저장해둔다.
해당 프론트 컨트롤러는 서블릿이므로, 해당 URL로 요청이 들어오면 service 메서드가 실행이 된다.
즉, 해당 요청으로 받은 URI를 파싱하여 해당 URI를 키로 하는 컨트롤러를 호출한다.(없으면 404)
이후 호출한 컨트롤러의 process 메서드를 호출하여 컨트롤러의 비즈니스 로직을 수행하고 알맞은 뷰(JSP) 파일로 포워딩한다.
결론
이번 리팩토링으로 비즈니스 로직과 뷰를 분리함과 동시에 중복되는 로직을 프론트 컨트롤러를 도입하여 해소 하였다.
다만 아직 모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 존재하는 단점이 있기에, 다음 V2에서는 이를 해결해 보겠다.
'Spring > MVC' 카테고리의 다른 글
[Spring] FrontController 도입하기 V3 - 서블릿 종속성 제거 (0) | 2025.06.23 |
---|---|
[Spring] FrontController 도입하기 V2 - 포워딩 중복 제거 (0) | 2025.06.23 |
[Spring] Front Controller 패턴 - 개요 (0) | 2025.06.22 |
[Spring] 서블릿과 JSP로 MVC 흉내내기 , 그리고 한계점 (0) | 2025.06.22 |
[Spring] MVC 패턴의 개요 - JSP는 만능이지만 만능이 아님 (1) | 2025.06.22 |