서블릿 종속성 제거
다음 코드는 이전 실습에서 Front Controller를 도입한 컨트롤러의 코드이다. 요청 파라미터 정보는 자바의 Map으로 대신 넘기기만 한다면 HttpServletRequest, HttpServletResponse이 더이상 필요하지 않다. 즉 서블릿 기술이 더이상 필요하지 않다.
그래서 이번에는 서블릿 기술을 사용하지 않고, request 객체를 Model로 사용하는 대신에 별도의 Model 객체인 ModelView를 만들어서 반환하도록 한다.
뷰 이름 중복 제거
앞선 실습에서는 컨트롤러에 /WEB-INF/views/~ 등 jsp 파일(View)의 경로가 중복되어 쓰이는 경우가 많았다. 그래서 컨트롤러에는 View의 가상 경로(논리 이름)를 반환하고, 실제 물리 경로의 이름은 Front Controller에서 처리하도록 한다.
향후 View의 위치가 변경되어도 Front Controller에서만 수정하면 된다.
ModelView 추가 및 컨트롤러 반영
기존 컨트롤러의 서블릿을 대체하기 위한 ModelView를 만든다. ModelView는 View의 이름과 View에 전달할 Model(Map<String, Object>)을 담고 있다. ModelView를 반환하는 ControllerV3 인터페이스를 정의하고 컨트롤러들이 상속 받도록 한다.
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
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 인터페이스를 준수하는 각 컨트롤러들에서 기술되는 비즈니스 로직은 동일하다.
각 컨트롤러마다 Front Controller로 전달하는 가상 경로인 viewName을 명시하고, ModelView의 model에 비즈니스 로직 결과(추가, 전체 리스트)를 반영한다.
public class MemberFormControllerV3 implements ControllerV3 {
@Override
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form");
}
}
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;
}
}
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;
}
}
Front Controller
다음은 FrontController이다.(Servlet)
Map에 담아 컨트롤러에 전달하며, 기존의 MyView를 컨트롤러가 아닌 Front Controller에서 반환하도록 하였다. 이제 논리적인 경로를 ViewResolver로 전달하면 실제 물리적인 경로를 포함하는 MyView를 반환한다.
@WebServlet(name="frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
...
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
// Map에 담아 Controller에게 넘겨줘야 한다
// 모든 파라미터 이름들을 다 가져온 후(getParameterNames), asIterator().forEachRemaining() 돌리면서 모든 파라미터를 다 꺼내온다
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 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;
}
}
이때 FrontController에서 MyView를 전달 받고 render를 호출하는데, 이때 model을 전달한다. 왜 그럴까? 뷰가 렌더링 되려면 모델이 필요하다. JSP는 request.getAttribute() 로 데이터를 조회하기 때문에, 모델의 데이터를 꺼내서 request.setAttribute() 로 담아둔다.
view.render(mv.getModel(), request, response);
모델에 들어있는 데이터를 다 꺼내서(modelToRequestAttribute), request의 setAttribute에 다 넣는다.
public class MyView {
private String viewPath;
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 > MVC ①' 카테고리의 다른 글
[Spring] MVC _ 어댑터 패턴 (0) | 2023.07.19 |
---|---|
[Spring] MVC _ 개발자를 위한 기능 개선 (0) | 2023.07.17 |
[Spring] MVC _ Front Controller 도입, View 분리 (0) | 2023.07.17 |
[Spring] 회원 관리 웹 어플리케이션 4 _ ServletMVC 패턴 (0) | 2023.07.15 |
[Spring] 회원 관리 웹 어플리케이션 3 _ JSP (0) | 2023.07.14 |