김영한님의 스프링 MVC 1편 강의를 듣고 복습 겸 정리하는 포스팅입니다!!
아직 공부하는 중인 학생이라 부족한 부분이 있을 수 있습니다.
혹시라도 틀린 부분이 있다면 언제든지 댓글로 남겨주세요. 감사합니다 🙇♂️
섹션 5. Model 추가 - V3
[이전 버전]
https://lyjduswls.tistory.com/55
[스프링 MVC] #5
김영한님의 스프링 MVC 1편 강의를 듣고 복습 겸 정리하는 포스팅입니다!!아직 공부하는 중인 학생이라 부족한 부분이 있을 수 있습니다. 혹시라도 틀린 부분이 있다면 언제든지 댓글로 남겨주세
lyjduswls.tistory.com
MemberSaveControllerV2 - 회원 정보 저장 컨트롤러
public class MemberSaveControllerV2 implements ControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView 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);
request.setAttribute("member", member);
return new MyView("/WEB-INF/views/save-result.jsp");
}
}
MemberSaveController의 경우 요청 파라미터 정보가 필요한것이지 사실 request,response가 당장 필요한 것은 아니다.
이번 단계에서 할 작업
1. 컨트롤러가 서블릿 기술을 전혀 사용하지 않도록 변경한다.
- 구현코드가 단순해지고, 테스트 코드의 작성이 쉬워진다.
- request.setAttribute() 대신 별도의 Model 객체로 분리한다.
- 요청 파라미터는 자바의 Map을 사용해 전달한다.
2. 뷰 이름의 중복을 제거한다.
- 기존의 컨트롤러는 물리 뷰 경로를 직접 반환한다.
- 모든 뷰 경로에 /WEB-INF/views/ 와.jsp가 모두 뷰에 중복되어 있다.
- 컨트롤러는 뷰의 "new-form"과 같은 논리 이름만 반환하고, 물리적 경로 조합은 프론트 컨트롤러에서 처리하도록 변경한다.
- /WEB-INF/views/new-form.jsp -> new-form
- /WEB-INF/views/save-result.jsp -> save-result
- /WEB-INF/views/members.jsp -> members
- 뷰 경로가 변경되더라도 프론트 컨트롤러만 수정하면 됨 → 변경 지점을 하나로 만들 수 있는 설계 = 좋은 설계
- 컨트롤러가 서블릿 기술과 뷰 경로에 의존하지 않게 되어 재사용성과 테스트성 향상된다.
V3 구조
- 클라이언트가 HTTP 요청을 전송한다.
- 프론트 컨트롤러가 매핑정보에 맞는 컨트롤러를 조회한다.
- 프론트 컨트롤러가 조회된 컨트롤러를 호출한다.
- 컨트롤러는 모델과 뷰가 섞여있는 ModelView 객체를 반환한다. (기존에는 view를 반환했다.)
- 프론트 컨트롤러가 뷰 리졸버를 호출한다.
- 뷰 리졸버에서 논리 이름을 실제경로로 바꾸고 MyView 객체를 반환한다.
- 프론트 컨트롤러가 MyView.render()를 호출한다.
- MyView 내부에서 JSP로 forward하여 HTML 응답을 생성하고 클라이언트에 전달한다.
ModelView
package hello.servlet.web.frontcontroller;
import java.util.HashMap;
import java.util.Map;
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;
}
}
ControllerV3
package hello.servlet.web.frontcontroller.v3;
import hello.servlet.web.frontcontroller.ModelView;
import java.util.Map;
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
ControllerV2와 다르게 ModelView를 반환하는 식으로 바뀌었다.
서블릿 API를 제거해, 서블릿 환경에 종속적이지 않게 개선되었다.
서블릿 API (HttpServletRequest, HttpServletResponse)에 직접 의존 ->서브릿 환경에 종속적이다.
MemberFormControllerV3 - 회원 등록 폼 컨트롤러
package hello.servlet.web.frontcontroller.v3.controller;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import java.util.Map;
public class MemberFormControllerV3 implements ControllerV3 {
@Override
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form");
}
}
ModelView의 viewName에 논리적인 이름을 넣는다.
MemberSaveControllerV3 - 회원 정보 저장 컨트롤러
package hello.servlet.web.frontcontroller.v3.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 java.util.Map;
public class MemberSaveControllerV3 implements ControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member);
return mv;
}
}
이전(위의 사진)에는 HttpServletRequest.getParameter()를 통해 요청 파라미터를 직접 꺼냈지만, V3구조에서는 프론트 컨트롤러가 요청 파라미터를 미리 추출해 Map 형태로 전달하기 때문에 컨트롤러는 단순히 paramMap.get("key")로 값을 꺼내쓰면 된다.
mv.getMoel().put("member", member);
모델은 단순한 Map 구조이므로, 뷰에서 필요한 member 객체를 모델에 담아 반환한다.
MemberListControllerV3- 회원 목록 조회 컨트롤러
package hello.servlet.web.frontcontroller.v3.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 java.util.List;
import java.util.Map;
public class MemberListControllerV3 implements ControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
List<Member> members = memberRepository.findAll();
ModelView mv = new ModelView("members");
mv.getModel().put("members", members);
return mv;
}
}
FrontControllerServletV3
package hello.servlet.web.frontcontroller.v3;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v3.controller.MemberFormControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberListControllerV3;
import hello.servlet.web.frontcontroller.v3.controller.MemberSaveControllerV3;
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;
@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;
}
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 static MyView viewResolver(String viewName) {
MyView view = new MyView("/WEB-INF/views/" + viewName + ".jsp");
return view;
}
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) {
MyView view = new MyView("/WEB-INF/views/" + viewName + ".jsp");
return view;
}
- 컨트롤러가 반환한 논리 이름을 실제 물리 뷰 경로로 변경한다.
- 논리 이름: "members"
- 물리 뷰 경로: "/WEB-INF/views/members.jsp"
- viewResolver()에서 실제 JSP 경로를 가진 MyView 객체를 반환한다.
view.render(mv.getModel(),request, response);
- 뷰 객체를 통해 HTML 화면을 렌더링한다.
- MyView.render()는 모델 정보도 함께 받는다.
- JSP는 request.getAttribute()로 데이터를 조회하기 때문에, 모델의 데이터를 꺼내서 request.setAttribute()로 담아둔다.
- JSP로 포워드해서 JSP를 렌더링한다.
MyView
package hello.servlet.web.frontcontroller;
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.Map;
public class MyView {
private String viewPath; //"/WEB-INF/views/new-form.jsp" 값은 이미 들어가있다.
public MyView(String viewPath) {
this.viewPath = viewPath;
}
public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
modelToRequestAttribute(model, request);
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
private static void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
model.forEach((key, value) -> request.setAttribute(key,value));
}
}
'Spring Study' 카테고리의 다른 글
[스프링 MVC] #8 (0) | 2025.05.10 |
---|---|
[스프링 MVC] #7 (0) | 2025.05.04 |
[스프링 MVC] #5 (1) | 2025.05.03 |
[스프링 MVC] #4 (0) | 2025.05.03 |
[스프링 MVC] #3 (2) | 2025.04.27 |