MVC 프레임워크 3

Jan 27, 2023

7 mins read

1. 프론트 컨트롤러 Version 3 - Model 추가

  • 서블릿 종속성 제거 : 요청 파라메터는 Map으로 받아서 처리하고 request 객체 대신 Model 객체를 구현한다.
    -> 코드가 단순해지고 테스트, 수정이 쉬워진다.

  • 뷰 이름 중복 제거 : 물리 위치 이름 대신 논리 이름을 받아서 처리한다. 물리 위치는 프론트컨트롤러에서만 처리한다.

    /WEB-INF/views/new-form.jsp -> new-form

  • (인프런 수업 듣는중)

  • hello.servlet.web.frontcontroller에 ModelView 객체를 만든다. 뷰이름과 뷰렌더링에 필요한 모델을 Map형태로 가져간다.

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;
	}
	
}
  • hello.servlet.web.frontcontroller.v3 패키지에 컨트롤러 ControllerV3 인터페이스를 만든다.
  • 이 인터페이스를 사용하는 컨트롤러는 뷰 이름과 뷰에 전달할 Model 데이터를 포함하는 ModelView 객체를 반환한다.
package hello.servlet.web.frontcontroller.v3;

import java.util.Map;

import hello.servlet.web.frontcontroller.ModelView;

public interface ControllerV3 {
	
	ModelView process(Map<String, String> paramMap);

}
  • hello.servlet.web.frontcontroller.v3.controller 패키지에 회원 등록 컨트롤러 MemberSaveControllerV3 생성한다.
package hello.servlet.web.frontcontroller.v3.controller;

import java.util.Map;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;

public class MemberFormControllerV3 implements ControllerV3{
	
@Override
	public ModelView process(Map<String, String> paramMap) {
		return new ModelView("new-form");
	}	

}
  • hello.servlet.web.frontcontroller.v3.controller 패키지에 회원 저장 컨트롤러 MemberSaveControllerV3 생성한다.
  • 파라미터 정보는 Map에 담고 이 Map을 모델에 담아서 프론트컨트롤러가 처리하도록 한다.
package hello.servlet.web.frontcontroller.v3.controller;

import java.util.Map;

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;

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;
	}
}
  • hello.servlet.web.frontcontroller.v3.controller 패키지에 회원 목록 컨트롤러 MemberListControllerV3 생성한다.
package hello.servlet.web.frontcontroller.v3.controller;

import java.util.List;
import java.util.Map;

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;

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

}
  • hello.servlet.web.frontcontroller.v3 패키지에 프론트 컨트롤러 FrontControllerServletV3를 생성한다.
package hello.servlet.web.frontcontroller.v3;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

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;

//front-controller/v3/으로 들어오는 모든 요청을 컨트롤 한다.
@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;
		}	

		//pramMap
		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); //v2 버전의 MyView객체 뷰에 모델을 추가한 메서드 render를 추가해야한다.
		
	}
	
	public 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;
	}
	
	public MyView viewResolver(String viewName) {
		return new MyView("/WEB-INF/views/" +viewName+ ".jsp");
	}
	
}
  • v2 버전에서 만든 뷰(MyView객체)에 모델을 추가한 메서드 render를 추가해야한다.
package hello.servlet.web.frontcontroller;

import java.io.IOException;
import java.util.Map;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

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

	//jsp에 모델에 있는 값을 전달하기 위해 request에 담아주는 작업을 해준다. (jsp는 request에서 값을 받기 때문에)
	private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
		model.forEach((key,value)->request.setAttribute(key, value)); //변수로 루프돌려서 request에 다 담음 
	}
	
	
}
  • 뷰와 모델이 다 추가되어 프론트컨트롤러에 많은 변화가 생겼다. 그 대신 각 비지니스 로직을 수행하는 컨트롤러는 훨씬 단순해졌다.

  • 개념적으로나 코드 보면서 따라가면 알것같은데 막상 0부터 다시 만들어 보라고하면 못할것같다. 계속 보면서 익숙해져야할듯.

  • http://localhost:8080/front-controller/v3/members
    http://localhost:8080/front-controller/v3/members/new-form
    접속해보면 잘 동작을 하는걸 확인할 수 있다.