스프링MVC

MVC 프레임워크 구현해 보기

salmon16 2023. 8. 7. 17:31

간단한 회원가입 시스템을 처리하는 시스템을 MVC패턴으로 구현해 보자

위 그림은 구현해야 할 MVC프레임워크 구조도 이다.

 

전체적인 과정

HTTP요청이 들어오면 FrontController가 호출된다.

요청이 들어오면 FrontController는 핸들러 매핑 정보에서 Url을 통해 핸들러를 받은 후 

핸들러의 타입을 얻기 위해 핸들러 어댑터 목록에서 핸들러 어댑터를 받은 후 

핸들러 어댑터를 호출한다. 그리고 핸들러에서 비즈니스 로직을 한 후 modelView를 FrontController에 반환한다

그럼 FrontController는 ViewResolver를 통해 Url을 반환받은 후 MyView를 통해 클라이언트에게 HTML응답을 전송한다.

 

하나씩 알아 보자! 여기서 핸들러는 컨트롤러라고 보면 된다!.

FrontController

MVC프레임워크를 만들기 위해 FrontController를 Servlet으로 만들 것이다.

FrontController는 각 handler들의 공통된 부분을 처리하고 전반적인 시스템을 통제한다.

각 요청 URL들과 핸들러들의 쌍을 저장해 두는 handlerMappingMap

각 핸들러들을 저장하는 handlerAdapters가 있다.

FrontController에서 핸들러 어댑터와 핸들러 매핑 정보를 저장해 둔다.

viewResolver메서드를 통해  Myview인스턴스를 얻은 후 Myview인스턴스를 통해 HTML을 클라이언트에게 보낸다.

@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*") ///front-controller/v5/ 로 시작하는 url 모두 해당 
public class FrontControllerServletV5 extends HttpServlet {
    private final Map<String, Object> handlerMappingMap = new HashMap<>();
    private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();

    public FrontControllerServletV5() { // 핸들러 매핑 정보, 핸들러 어댑터 초기화
        initHandlerMappingMap();
        initHandlerAdapters();
    }
    private void initHandlerMappingMap() { //url 등록
        handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
        handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
        handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());

        handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
        handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
        handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
    }
    private void initHandlerAdapters() { // 핸들러 타입 어댑터 등록
        handlerAdapters.add(new ControllerV3HandlerAdapter());
        handlerAdapters.add(new ControllerV4HandlerAdapter());
    }
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        Object handler = getHandler(request); //핸들러 get
        if (handler == null) { // 핸들러가 없을 때 예외 처리
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        MyHandlerAdapter adapter = getHandlerAdapter(handler); // 핸들러에 맞는 어댑터 get
        ModelView mv = adapter.handle(request, response, handler); // 로직 수행후 Modelview 반환 받기
        MyView view = viewResolver(mv.getViewName()); // viewResolver를 통해 Myview get
        view.render(mv.getModel(), request, response); // Myview 를 통해 클라이언트에게 Html전송
    }
    private Object getHandler(HttpServletRequest request) { // get 핸들러
        String requestURI = request.getRequestURI();
        return handlerMappingMap.get(requestURI);
    }
    private MyHandlerAdapter getHandlerAdapter(Object handler) { // for문을 통해 어탭터 타입이 맞는지 확인후 맞으면 반환
        for (MyHandlerAdapter adapter : handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
        throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler); 
    }
    private MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }
}

핸들러 매핑 정보

FrontController가 HTTP요청에서 URL을 얻어 핸들러 Map에서 맞는 핸들러를 return 한다.

private void initHandlerMappingMap() { //url 등록
    handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
    handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
    handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());

    handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
    handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
    handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
}
private Object getHandler(HttpServletRequest request) { // get 핸들러
    String requestURI = request.getRequestURI();
    return handlerMappingMap.get(requestURI);
}

핸들러 어댑터 목록

핸들러 어댑터 목록에는 여러 가지 다르게 구현된(애노테이션기반 핸들러, 모델뷰를 반환하는 핸들러, string을 반환하는 핸들러 등) 핸들러의 타입이 저장되어 있다.

요청 사항에 맞는 핸들러 어댑터를 찾아 Front Controller에 반환한다.

private void initHandlerAdapters() { // 핸들러 타입 어댑터 등록
    handlerAdapters.add(new ControllerV3HandlerAdapter());
    handlerAdapters.add(new ControllerV4HandlerAdapter());
}
private MyHandlerAdapter getHandlerAdapter(Object handler) { // for문을 통해 어탭터 타입이 맞는지 확인후 맞으면 반환
    for (MyHandlerAdapter adapter : handlerAdapters) {
        if (adapter.supports(handler)) {
            return adapter;
        }
    }
    throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler);
}

핸들러 어댑터

핸들러 어댑터에서는 각 다르게 구현된 핸들러들을 같은 동작을 해 줄 수 있도록 처리를 해주는 것이다.

예를 들어 핸들러 V4의 return 타입은 HTML url 스트링인데 어댑터에서 이를 받아 modelView에 담아서 frontController에 반환을 해 주는 것이다. 또는  V4핸들러의 process메서드 인자로 paramMap이라는 인자가 필요한데 frontController에서 HttpServletRequest로 넘어온 정보를 createParamMap 메서드를 통해 Map에 담아주는 것도 한다.

@Override
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
    ControllerV4 controller = (ControllerV4) handler;
    Map<String, String> paramMap = createParamMap(request);
    HashMap<String, Object> model = new HashMap<>();

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

    ModelView mv = new ModelView(viewName);
    mv.setModel(model);

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

 

핸들러(컨트롤러)

핸들러에서는 각 url에 맞는 비즈니스를 수행하고 핸들러 어댑터에게 결과물을 반환한다.

예를 들어 V4의 MemberSaveController에서는 paramMap에서 정보를 get 해서 memberRepository에 저장을 하고 저장된 결과를 보여주는 url을 어댑터에게 return 한다.

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

ViewResolver

모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있고 깔끔하지 않기 때문에 뷰를 처리하는 객체를 만든다.

viewResolver에서는 Myview객체를 url을 통해 만든다.

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

MyView클래스는 HttpServletRequset에 정보를 담고 RequsetDispatcher.forward 메서드를 통해 클라이언트에게 HTML 등의 정보를 보낸다.

 

출처 : 스프링MVC 1편 - 백엔드 웹 개발 핵심 기술 김영한

'스프링MVC' 카테고리의 다른 글

핸들러 매핑과 핸들러 어댑터  (0) 2023.08.08
스프링 MVC 구조 이해  (0) 2023.08.08
MVC패턴  (0) 2023.08.04
JSP 사용하기  (0) 2023.08.02
HttpServletResponse 사용  (0) 2023.08.02