728x90

들어가며,

어플리케이션을 만드는데 기본적으로 사용되는 MVC 패턴을 공부하기 전에 Servlet과 JSP에서 MVC패턴으로 변화되는 과정에서 중요한 패턴인 프론트 컨트롤러에 대해 공부해보려고 한다.

MVC패턴은 request를 받아서 컨트롤러가 정상적인 HTTP 요청인지 확인 후 서비스, 리포지토리 계층에 요청을 전달한다. 그러면 서비스 계층에서는 비즈니스 로직을, 리포지토리에서는 DB로 전달을 하게 된다.
이 동작을 통해 Model이라는 도메인에 데이터를 전달하고 참조할 수 있게 되는 것이다.
마지막으로 View에서는 response를 내보내면 client가 확인할 수 있다.

프론트 컨트롤러란?

 

이전에 request를 Servlet으로 받아 처리할 때는 각 컨트롤러가 직접 요청을 받아 처리했는데 Front Controller를 도입하면 먼저 Front Controller에서 클라이언트의 모든 요청 값들을 받아서 각 컨트롤러에 맞게 전달해준다.
이를 통해 요청 처리의 일관성을 유지하고 중복 코드를 줄일 수 있으며, 보안, 로깅 등의 부가적인 작업을 효율적으로 수행할 수 있다.

 

@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 {
    
    System.out.println("FrontControllerServletV1.service");
    String requestURI = request.getRequestURI();
    
    ControllerV1 controller = controllerMap.get(requestURI);
    if (controller == null) {
    	response.setStatus(HttpServletResponse.SC_NOT_FOUND);
    	return;
    }
    controller.process(request, response);
    }
}

 

FrontControllerServletV1 생성자를 통해 등록된 controllerMap을 통해 클라이언트 요청을 각 컨트롤러에 매핑한다.
service 메서드는 HttpServletRequest와 Response를 받아서 URI를 조회하고 controllerMap에서 객체를 찾아온다.
객체 값이 null인 경우 NOT_FOUND 예외 처리를 한다.
request와 response를 받아 process 메서드를 동작한다.

 

controllerMap 구성

  • key: 매핑 URL
  • value: 호출될 컨트롤러 

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

 

MyView라는 중간 단계를 두어 Controller가 Jsp에 직접 전달하지 않고 Front Controller를 거치게 설계됐다.

 

@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/
v2/*")
public class FrontControllerServletV2 extends HttpServlet {
    private Map<String, ControllerV2> controllerMap = new HashMap<>();
    public FrontControllerServletV2() {
        controllerMap.put("/front-controller/v2/members/new-form", new MemberFormControllerV2());
        controllerMap.put("/front-controller/v2/members/save", new MemberSaveControllerV2());
        controllerMap.put("/front-controller/v2/members", new MemberListControllerV2());
    }
    
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        
        ControllerV2 controller = controllerMap.get(requestURI);
        if (controller == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        MyView view = controller.process(request, response);
        view.render(request, response);
        }
    }

 

이제 각각의 Controller는 MyView만 반환하면, 프론트 컨트롤러에서 render메서드를 통해 일관되게 처리해준다.

 

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

 

서블릿 종속성을 제거하는 것이 목적이다.
1. ModelView를 통해 컨트롤러는 논리 View 이름을 반환하게 되며, 이를 View Resolver를 통해 실제 View 경로로 변경해준다.
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;
    	}
    }​

 


2. 요청 파라미터에서 서블릿을 제거하고 Map을 통해 넘겨준다.

 

Spring MVC 구조

 

Spring MVC 구조에서는 Front Controller가 Dispatcher Servlet으로 구성되어 있다. 
이외에도 어댑터를 통해 핸들러(컨트롤러)를 유연하게 사용할 수 있다는 장점이 있다.

+ Recent posts