👩‍💻 BackEnd/🌿 스프링 [Spring]

[Spring MVC] Section 4. MVC 프레임워크 만들기

minhe2810 2024. 1. 13. 10:10

프론트 컨트롤러 패턴 소개 

 

프론트 컨트롤러 도입 전 

  • 모든 입구로 요청이 다 들어오니까 들어오는 곳 마다 필요한 로직을 작성해야하는데 이 코드가 중복될 우려가 있다.

 

프론트 컨트롤러 도입 후 

  • 공통 로직을 하나의 프론트 컨트롤러에 작성해둠. 그리고 여기서 어떤 컨트롤러를 호출할지까지 작성. 

 

프론트 컨트롤러 패턴 특징 

  • 입구를 하나로 함으로써 공통 처리 가능해짐. 
  • 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출하게됨. 
  • 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨. 

 

스프링 웹 MVC와 프론트 컨트롤러 

  • 스프링 웹 MVC 의 DispatcherServlet 이 FrontController 패턴으로 구현되어있음. (오호!) 

 

프론트 컨트롤러 도입 - v1 

 

 

 

 

(++ 여기 부분은 강의 내용을 한 번 더 듣고 필기를 해야할 것 같음)

 


단순하고 실용적인 컨트롤러 - v4 

지금까지 만든 v3 컨트롤러는 

  • 서블릿 종속성을 제거
  • 뷰 경로의 복잡성을 제거 

그런데 실제 컨트롤러의 인터페이스를 구현하는 개발자의 입장에서 보면, 항상 ModelView 객체를 생성하고 반환해야하는 부분이 조금 번거로울 수 있다. 

 

따라서 이를 실용적으로 변경해보자. 

 

v4 의 구조

  • 기본적인 구조는 v3와 같지만, 컨트롤러가 ModelView 를 반환하지 않고, 'ViewName'을 반환한다. 
  • 이번 버전에는 인터페이스에 ModelView가 없다. model 객체는 (컨트롤러 인터페이스의 process 메서드에서) 파라미터로 전달되기 때문에 그냥 사용하면 되고, 결과로 뷰의 이름만 반환해주면 된다.

 

먼저 인터페이스 작성 

public interface ControllerV4 {

    /**
     *
     * @param paramMap
     * @param model
     * @return viewName
     */
    String process(Map<String, String> paramMap, Map<String, Object> model);
}
public class MemberFormControllerV4 implements ControllerV4 {

    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        return "new-form";
    }
}
  • 프론트 컨트롤러에서 컨트롤러를 호출할 때 model을 같이 넣어서 넘겨줌. 따라서 그냥 사용하면 됨. 
  • viewName 을 반환함. 

 

package com.hello.servlet.web.frontcontroller.v4.controller;

import com.hello.servlet.domain.member.Member;
import com.hello.servlet.domain.member.MemberRepository;
import com.hello.servlet.web.frontcontroller.v4.ControllerV4;

import java.util.Map;

public class MemberSaveControllerV4 implements ControllerV4 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        String username = paramMap.get("username");
        int age = Integer.parseInt(paramMap.get("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        model.put("member", member);
        return "save-result";
    }
}

 

 

FrontControllerV4 

@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {

    private Map<String, ControllerV4> controllerMap = new HashMap<>();

    // 매핑 정보 조회
    public FrontControllerServletV4() {
        controllerMap.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
        controllerMap.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
        controllerMap.put("/front-controller/v4/members", new MemberListControllerV4());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FrontControllerServletV4.service");

        String requestURI = request.getRequestURI();

        ControllerV4 controller = controllerMap.get(requestURI);

        if (controller == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // paramMap
        Map<String, String> paramMap = createParamMap(request);
        Map<String, Object> model = new HashMap<>();  // 이 부분만 추가됨. model 넘겨주기 위함.

        String viewName = controller.process(paramMap, model);

        MyView view = viewResolver(viewName);

        view.render(model, request, response);
    }

    private static MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }

    // method 로 레벨 맞춰주기
    // request 객체에 있는 파라미터 이름들을 모두 꺼내어 paramName 에 저장
    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;
    }
}

 

 

 

  • model 객체 전달
    • Map<String, Object> model = new HashMap<>(); 
    • 모델 객체를 프론트 컨트롤러에서 생성해서 넘겨준다. 컨트롤러에서 모델 객체에 값을 담으면 여기에 그대로 담겨있게 된다. 
  • View의 논리 이름을 직접 반환 (viewResolver) 
        String viewName = controller.process(paramMap, model);

        MyView view = viewResolver(viewName);
  private static MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }
  • 컨트롤러가 view의 논리 이름을 반환하므로 이 값을 사용하여 실제 물리 뷰를 찾을 수 있게 되는 것임. 

 

 


다음 단계에서는 Adapter라는 개념을 적용해볼 것이고, 

항상 컨트롤러의 모양이 정해져 있어서 다양한 형태의 값을 보내지 못한다는 점을 개선해볼 예정이라고 한다!