[Spring] FrontController 도입하기 V3 - 서블릿 종속성 제거
[Spring] FrontController 도입하기 V2 - 포워딩 중복 제거https://bdisappointed.tistory.com/154 [Spring] FrontController 도입하기 V1[Spring] 서블릿과 JSP로 MVC 흉내내기 , 그리고 한계점서블릿과 JSP를 통해 MVC를 구현 해
bdisappointed.tistory.com
지난번에 이어 ModelView를 반환하는 부분을 리팩토링하여 개발자 친화적으로 바꿔보자!
어떻게 바꿀꺼냐면요..

기본적인 구조는 V3와 같지만, 컨트롤러가 ModelView를 반환하는 대신에 뷰 이름만 반환하도록 해보자.
V4 인터페이스 변경
package hello.servlet.web.frontcontroller.v4;
import java.util.Map;
public interface ControllerV4 {
String process(Map<String, String> paramMap, Map<String, Object> model);
}
V3와 차이점은 반환 타입이 String 이라는 점이다.
추가적으로, 모델을 파라미터로 넘겨 받는다.
컨트롤러
회원 등록
package hello.servlet.web.frontcontroller.v4.controller;
import hello.servlet.web.frontcontroller.v4.ControllerV4;
import java.util.Map;
public class MemberFormControllerV4 implements ControllerV4 {
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
return "new-form";
}
}
회원 저장
package hello.servlet.web.frontcontroller.v4.controller;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import hello.servlet.web.frontcontroller.v4.ControllerV4;
import java.util.Map;
public class MemberSaveControllerV4 implements ControllerV4 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
model.put("member", member);
return "save-result";
}
}
회원 조회
package hello.servlet.web.frontcontroller.v4.controller;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import hello.servlet.web.frontcontroller.v4.ControllerV4;
import java.util.List;
import java.util.Map;
public class MemberListControllerV4 implements ControllerV4 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
List<Member> members = memberRepository.findAll();
model.put("members", members);
return "members";
}
}
모든 컨트롤러가 이번엔 ModelView가 아닌 String의 뷰의 논리적 이름만 반환한다.
자세한건 프론트 컨트롤러를 보면 이해가 쉽다 !
프론트 컨트롤러
package hello.servlet.web.frontcontroller.v4;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v4.controller.MemberFormControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberSaveControllerV4;
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.SC_NOT_FOUND;
@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
private Map<String, ControllerV4> controllerMap = new HashMap<>();
public FrontControllerServletV4() {
controllerMap.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
controllerMap.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
controllerMap.put("/front-controller/v4/members", new MemberListControllerV4());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
// 다형성 활용
ControllerV4 controller = controllerMap.get(requestURI);
// 만약 없으면?
if (controller == null) {
response.setStatus(SC_NOT_FOUND);
return;
}
// paraMap은 Reqeust에 있는 모든 쿼리의 키-값 정보
Map<String, String> paramMap = createParamMap(request); // request 에 있는 파라미터를 모두 호출
Map<String, Object> model = new HashMap<>(); // "member":member 객체
String viewName = controller.process(paramMap, model); // 컨트롤러에 paraMap이랑 model을 넘긴다?
MyView view = viewResolver(viewName);
view.render(model,request,response);
}
private static Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames().asIterator()
.forEachRemaining(paramName -> paramMap.put(paramName,
request.getParameter(paramName)));
return paramMap;
}
private static MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}
동작 과정
URI를 받고, 컨트롤러를 찾는 과정은 동일하다.
또한, 먼저 request에서 모든 파라미터를 받아 paramMap을 만드는 과정도 동일하다.
그리고 변경 사항이 등장하는데, 컨트롤러에서 뷰로 전달할 데이터를 담을 모델을 프론트 컨트롤러단에서 미리 생성한다.
이후 paramMap과 model을 넘겨 이후 컨트롤러에서 뷰로 전달할 데이터는 paramMap을 통해 비즈니스 로직이 수행되며, 해당 비즈니스 로직을 통해 뷰로 전달해야하는 데이터를 프론트 컨트롤러 단에 존재하는 Map에 저장을 한다는 점에서 변경 사항이 존재한다.
컨트롤러는 비즈니스 로직 수행 후 뷰의 논리적 이름만 반환하고, 해당 논리적 뷰 이름은 viewResolver 메서드를 통해 절대 경로로 설정 된 뒤에 포워딩하는 render 로직을 수행한다.
결론
우리는 FrontContoller 단에서 생성한 Model을 통해 추가적은 ModelView 객체 없이 단순 View 이름만 반환 받아 포워딩이 가능해졌다.
즉, 모델을 미리 만들어 파라미터로 넘기고, 뷰 이름만 반환 받자 ! 라는 아이디어로 개발자가 편리한 코드가 작성이 되었다.
다음에는 각각 다른 인터페이스를 처리할 수 있는 유연한 컨트롤러로 리팩토링 해보겠다 !
'Spring > MVC' 카테고리의 다른 글
| [Spring] 내가 만든 MVC 와 Spring MVC 차이 (1) | 2025.06.23 |
|---|---|
| [Spring] FrontController V5(完) - 다양한 인터페이스 구현체 처리하기 (2) | 2025.06.23 |
| [Spring] FrontController 도입하기 V3 - 서블릿 종속성 제거 (0) | 2025.06.23 |
| [Spring] FrontController 도입하기 V2 - 포워딩 중복 제거 (0) | 2025.06.23 |
| [Spring] FrontController 도입하기 V1 (0) | 2025.06.23 |