Spring/[인프런 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술]
[인프런 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술] MVC 프레임워크 만들기
h2boom
2024. 10. 3. 16:12
MVC 프레임워크 만들기
프론트 컨트롤러 패턴
- Front Controller : 공통 로직을 프론트 컨트롤러가 모두 처리를 하며 클라이언트의 요청에 맞는 컨트롤러를 찾아서 호출해준다.
- 입구를 하나로 할 수 있으며 공통 로직을 처리할 수 있게 된다.
- 기존과 달리 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 된다.
- 스프링 MVC의 DispatcherServlet이 Front Controller 패턴으로 구현되어 있다.
프론트 컨트롤러 도입 - V1
@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 {
String requestURI = request.getRequestURI();
ControllerV1 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
controller.process(request, response);
}
}
- urlPattern에서 v1/* 이라고 하면 v1을 포함한 v1 하위 URI까지 모두 포함하게 된다.
- 각 Controller들은 인터페이스를 구현해서 만들어졌기에 다형성을 활용할 수 있다.
- 해당 URI와 일치하는 컨트롤러를 Map에서 찾아서 참조를 반환해줌으로 다형성을 활용했다.
- 리팩토링을 할 때 주의할 점
- 같은 레벨끼리만 개선을 해야한다.
- 구조를 개선할 때는 구조만 개선하고 기존 코드는 최대한 유지시켜야 한다.
- 이후 문제가 없을 경우 코드 내 세부적인 것들을 개선해 나가는 것이 좋다.
- 같은 레벨끼리만 개선을 해야한다.
View 분리 - V2
- V1 코드와 달리 각 Controller에서 포워드를 하는 것이 아닌 MyView라는 포워드를 해주는 별도의 클래스를 사용한다.
- 각 컨트롤러의 ViewPath 관련 중복 코드를 제거
Model 추가 - V3
- FrontController에서만 서블릿을 사용하여 공통 로직을 처리하므로 각 컨트롤러는 request, response가 필요없기에 서블릿 종속성 제거
- 요청 파라미터 정보는 Map으로 대신 넘긴다.
- request의 Model 대신 별도의 Model을 만들어서 사용
- View 경로 중 중복을 제거하고 컨트롤러에서 뷰의 논리 이름을 반환하도록 처리
- 로직 중 디테일한 로직들은 별도의 메소드로 추출해서 사용하는 것이 좋다.
//Model 역할과 forward 역할을 하는 클래스
public class ModelView {
private String viewName;
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName) {
this.viewName = viewName;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public Map<String, Object> getModel() {
return model;
}
public void setModel(Map<String, Object> model) {
this.model = model;
}
}
- 별도의 model을 만들어서 데이터를 담아 view에 전달해준다.
//FrontController
@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
private Map<String, ControllerV3> controllerMap = new HashMap<>();
public FrontControllerServletV3() {
controllerMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
controllerMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
controllerMap.put("/front-controller/v3/members", new MemberListControllerV3());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV3 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
//paramMap
//디테일한 로직은 메소드 추출해주기
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
String viewName = mv.getViewName();//논리이름
MyView view = viewResolver(viewName);
view.render(mv.getModel(), request, response);
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
private 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;
}
}
- paramMap은 request의 파라미터로 넘어온 쿼리 파라미터 형식의 데이터를 저장하는 Map이다.
- viewResolver() 메소드는 중복을 제거한 view 논리명을 view 물리 명으로 바꿔주는 메소드이다.
- 나중에 경로가 변경되더라도 각 controller의 코드를 고치지 않고 viewResolver() 코드만 고치면 되기에 유지보수에 좋다.
- 직접 만든 model을 MyView로 넘겨주면 JSP에서 데이터를 사용하기 위해서 MyView에서 model의 모든 데이터를 꺼내 request.setAttribute()를 통해 뷰 렌더링에 필요한 model로 변환시켜주는 작업을 한다.
단순하고 실용적인 컨트롤러 - V4
- 단순하고 편리함을 위해 ModelView를 사용해서 반환해주지 않고 FrontController에서 빈 model 객체를 미리 넘겨주고 ViewName만 반환하도록 설계
- FrontController에서 넘겨준 model 객체를 각 컨트롤러에서 값을 넣으면 그대로 담겨져 있다.
유연한 컨트롤러1 - V5
- 어댑터 패턴을 통해 다양한 버전(방식)의 컨트롤러를 처리할 수 있도록 설계
- 어댑터 패턴 : 호환되지 않는 인터페이스들을 연결하는 디자인 패턴이다.
- 핸들러 어댑터 : 어댑터 역할을 하며 다양한 종류의 컨트롤러를 호출할 수 있도록 해준다.
- 핸들러 : 컨트롤러보다 포괄적인 개념으로 유사하다.
- 이전 버전들에서는 FrontController가 실제 컨트롤러를 호출했지만 V5에서는 어댑터를 통해서 실제 컨트롤러가 호출된다.
//어댑터 인터페이스
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws ServletException, IOException;
}
- supports()는 컨트롤러를 파라미터로 넘겨서 어댑터가 해당 컨트롤러를 처리할 수 있는지 판단하는 메소드이다.
- handle()은 실제 컨트롤러를 호출하고 ModelView를 반환한다.
출처 : [인프런 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술]
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의 | 김영한 - 인프런
김영한 | 웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습
www.inflearn.com