👩💻 BackEnd/🌿 스프링 [Spring]
[Spring MVC] Section 4. MVC 프레임워크 만들기
minhe2810
2024. 1. 13. 10:10
프론트 컨트롤러 패턴 소개
프론트 컨트롤러 도입 전
- 모든 입구로 요청이 다 들어오니까 들어오는 곳 마다 필요한 로직을 작성해야하는데 이 코드가 중복될 우려가 있다.
프론트 컨트롤러 도입 후
- 공통 로직을 하나의 프론트 컨트롤러에 작성해둠. 그리고 여기서 어떤 컨트롤러를 호출할지까지 작성.
프론트 컨트롤러 패턴 특징
- 입구를 하나로 함으로써 공통 처리 가능해짐.
- 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출하게됨.
- 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨.
스프링 웹 MVC와 프론트 컨트롤러
- 스프링 웹 MVC 의 DispatcherServlet 이 FrontController 패턴으로 구현되어있음. (오호!)
프론트 컨트롤러 도입 - v1
(++ 여기 부분은 강의 내용을 한 번 더 듣고 필기를 해야할 것 같음)
단순하고 실용적인 컨트롤러 - v4
지금까지 만든 v3 컨트롤러는
- 서블릿 종속성을 제거
- 뷰 경로의 복잡성을 제거
그런데 실제 컨트롤러의 인터페이스를 구현하는 개발자의 입장에서 보면, 항상 ModelView 객체를 생성하고 반환해야하는 부분이 조금 번거로울 수 있다.
따라서 이를 실용적으로 변경해보자.
- 기본적인 구조는 v3와 같지만, 컨트롤러가 ModelView 를 반환하지 않고, 'ViewName'을 반환한다.
- 이번 버전에는 인터페이스에 ModelView가 없다. model 객체는 (컨트롤러 인터페이스의 process 메서드에서) 파라미터로 전달되기 때문에 그냥 사용하면 되고, 결과로 뷰의 이름만 반환해주면 된다.
먼저 인터페이스 작성
public interface ControllerV4 {
/**
*
* @param paramMap
* @param model
* @return viewName
*/
String process(Map<String, String> paramMap, Map<String, Object> model);
}
public class MemberFormControllerV4 implements ControllerV4 {
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
return "new-form";
}
}
- 프론트 컨트롤러에서 컨트롤러를 호출할 때 model을 같이 넣어서 넘겨줌. 따라서 그냥 사용하면 됨.
- viewName 을 반환함.
package com.hello.servlet.web.frontcontroller.v4.controller;
import com.hello.servlet.domain.member.Member;
import com.hello.servlet.domain.member.MemberRepository;
import com.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";
}
}
FrontControllerV4
@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 {
System.out.println("FrontControllerServletV4.service");
String requestURI = request.getRequestURI();
ControllerV4 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// paramMap
Map<String, String> paramMap = createParamMap(request);
Map<String, Object> model = new HashMap<>(); // 이 부분만 추가됨. model 넘겨주기 위함.
String viewName = controller.process(paramMap, model);
MyView view = viewResolver(viewName);
view.render(model, request, response);
}
private static MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
// method 로 레벨 맞춰주기
// request 객체에 있는 파라미터 이름들을 모두 꺼내어 paramName 에 저장
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;
}
}
- model 객체 전달
- Map<String, Object> model = new HashMap<>();
- 모델 객체를 프론트 컨트롤러에서 생성해서 넘겨준다. 컨트롤러에서 모델 객체에 값을 담으면 여기에 그대로 담겨있게 된다.
- View의 논리 이름을 직접 반환 (viewResolver)
String viewName = controller.process(paramMap, model);
MyView view = viewResolver(viewName);
private static MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
- 컨트롤러가 view의 논리 이름을 반환하므로 이 값을 사용하여 실제 물리 뷰를 찾을 수 있게 되는 것임.
다음 단계에서는 Adapter라는 개념을 적용해볼 것이고,
항상 컨트롤러의 모양이 정해져 있어서 다양한 형태의 값을 보내지 못한다는 점을 개선해볼 예정이라고 한다!