4-3. MVC 프레임워크 - Model 추가

shin·2025년 6월 15일

Spring MVC

목록 보기
16/25

1) Model 추가

(1) 서블릿 종속성 제거

  • 컨트롤러 입장에서 HttepServletRequest, HttpServletResponse는 필요하지 않음
  • 요청 파라미터 정보는 자바의 Map으로 대신 넘기도록 하면 지금 구조에서는 컨트롤러가 서블릿 기술을 몰라서 동작할 수 있음
  • response 객체를 Model로 사용하는 대신에 별도의 Model 객체를 만들어서 반환하면 됨
  • 구현하는 컨트롤러가 서블릿 기술을 전혀 사용하지 않도록 변경하면, 구현 코드는 매우 단순해지고 테스트 코드 작성도 쉬워짐

(2) 뷰 이름 중복 제거

  • 컨트롤러에서 지정하는 뷰 이름에 중복이 있는 것을 확인할 수 있음
  • 컨트롤러는 뷰의 논리 이름을 반환하고, 실제 물리 위치의 이름은 프론트 컨트롤러에서 처리하도록 단순화
  • 이렇게 하면 향후 뷰의 폴더 위치가 함께 이동해도 프론트 컨트롤러만 고치면 됨

/WEB-INF/views/new-form.jsp new-form
/WEB-INF/views/save-result.jsp save-result
/WEB-INF/views/members.jsp members



2) V3 구조


ModelView

  • 지금까지 컨트롤러에서 서블릿에 종속적인 HttpServletRequest를 사용했음

    • 그리고 Model도 requestl.setAttribute()를 통해 데이터를 저장하고 뷰에 전달함
  • 서블릿의 종속성을 제거하기 위해 Model을 직접 만들고, 추가로 View 이름까지 전달하는 객체를 만들어야 함

    • 이번 버전에서는 컨트롤러에서 HttpServletRequest를 사용할 수 없음
    • 따라서 직접 request.setAttribute()를 호출할 수도 없기 때문에, Model이 별도로 필요함
  • 참고로 ModelView 객체는 다른 버전에서도 사용하므로 패키지를 frontcontroller에 둠

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;
	}
}
  • 뷰의 이름과 뷰를 렌더링할 때 필요한 Model 객체를 가지고 있음
  • model은 단순히 map으로 되어 있으므로 컨트롤러에서 뷰에 필요한 데이터를 key, value로 넣어주면 됨

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);
}
  • 이 컨트롤러는 서블릿 기술을 전혀 사용하지 않음
    • 따라서 구현이 매우 단순해지고, 테스트 코드 작성시 테스트하기 쉬움
  • HttpServletRequest가 제공하는 파라미터는 프론트 컨트롤러가 paramMap에 담아서 호출해주면 됨
  • 응답 결과로 뷰 이름과 뷰에 전달할 Model 데이터를 포함하는 ModelView 객체를 반환하면 됨

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를 생성할 때 new-form이라는 view의 논리적인 이름을 지정함
  • 실제 물리적인 이름은 프론트 컨트롤러에서 처리함

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;
	}
}

paramMap.get("username");

  • 파라미터 정보는 map에 담겨있음
  • map에서 필요한 요청 파라미터를 조회하면 됨

mv.getModel().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 javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.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 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 MyView viewResolver(String viewName) {
    
		return new MyView("/WEB-INF/views/" + viewName + ".jsp");
	}
}

view.render(mv.getModel(), request, response) 코드에서 컴파일 오류가 발생할 것임

  • 다음 코드를 참고해서 MyView 객체에 필요한 메서드 추가

createParamMap()

  • HttpServletRequest에서 파라미터 정보를 꺼내서 Map으로 변환함
  • 그리고 해당 Map(paramMap)을 컨트롤러에 전달하면서 호출함

뷰 리졸버

MyView view = viewResolver(viewName)

  • 컨트롤러가 반환한 논리 뷰 이름을 실제 물리 뷰 경로로 변경
  • 그리고 실제 물리 경로가 있는 MyView 객체를 반환함
  • 논리 뷰 이름 : members
  • 물리 뷰 경로 : /WEB-INF/views/members.jsp

view.render(mv.getModel(), request, response)

  • 뷰 객체를 통해서 HTML 화면을 렌더링함
  • 뷰 객체의 render()는 모델 정보도 함께 받음
  • JSP는 request.getAttribute()로 데이터를 조회하기 때문에, 모델의 데이터를 꺼내서 request.setAttribute()로 담아둠
  • JSP로 포워드해서 JSP를 렌더링함

MyView

package hello.servlet.web.frontcontroller;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

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 void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
    
		model.forEach((key, value) -> request.setAttribute(key, value));
	}
}

실행

profile
Backend development

0개의 댓글