카테고리 없음

Spring MVC - 김영한 백엔드 (3)

jmboy 2024. 4. 22. 19:39

이전 게시물의 V3에 이어 개선해낸 V4, V5 에 대해 코드와 함께 설명을 하겠다!

V4

package hello.servlet.web.frontcontroller.v4;

import java.util.HashMap;
import java.util.Map;

public interface ControllerV4 {
    /**
     * @param paramMap
     * @param model
     * @return viewName
     */
    String process(Map<String, String> paramMap, Map<String, Object> model); // model 도 넘겨줌.뷰의 이름만 전달.
}
  • Controller 계층에서 model 도 같이 전달해준다.
package hello.servlet.web.frontcontroller.v4;

import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v4.controller.MemberFormControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberSaveControllerV4;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

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

@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*") // * -> v1 하위 모든 요청
public class FrontControllerServletV4 extends HttpServlet {

    private Map<String, ControllerV4> controllerMap = new HashMap<>(); // 요청 URI에 해당하는 controller를 찾기 위한 맵

    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 {

        request.getRequestURI(); // 요청 URI -> new-form

        // get으로 해당하는 controller 찾기
        ControllerV4 controller = controllerMap.get(request.getRequestURI()); // 요청 URI에 해당하는 controller 찾기 -> new form 된다.
        if (controller == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);// 404 처리
            return;
        }

//        request.getParameterNames().asIterator()
//                .forEachRemaining(paramName -> System.out.println(paramName + "=" + request.getParameter(paramName)));
        //paramMap
        // form 에 값을 넣었을때.
        Map<String, String> paramMap = createParamMap(request); // 파라미터 값을 다 넣음

        Map<String, Object> model = new HashMap<>(); // model 생성
        String viewName = controller.process(paramMap,model);// controller 호출 

        MyView view = viewResolver(viewName);// 물리이름 new-form.jsp

        view.render(model, request,response); // view 호출 jsp 호출
    }

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

    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))); // 파라미터에 있는 값을 다 꺼내서 paramMap에 저장
        return paramMap;
    }
}

Setting

  • 기존과 같이 URI 에 해당하는 controller를 찾기 위한 맵을 생성한다.
  • 생성자를 통해서 map에 controller와 URI 를 넣어준다.

Service

new-form

  • V3와 동일하게 controller 를 찾는다.
  • 이후 request에 파라미터가 아무것도 없기 때문에 빈 맵과 함께 controller 에 들어간다.
  • 또한 model 생성을 한 후 같이 제공한다. 현재는 빈 맵
package hello.servlet.web.frontcontroller.v4.controller;

import hello.servlet.web.frontcontroller.v4.ControllerV4;

import java.util.HashMap;
import java.util.Map;

public class MemberFormControllerV4 implements ControllerV4 {
    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        return "new-form";
    }
}
  • new-form 을 return 하고 , 해당 return 값은 viewName 이다.
  • viewName에 알맞은 jsp를 resolve하고, view 를 렌더링 한다.

→ new-form.jsp 에서 save로 post를 보낸다.


save

  • 해당 URI가 save를 호출 , saveController 를 호출한다.
  • parameter 값에 username, age 가 담겨서 온다.
  • 이후 빈 모델 맵을 같이 보낸다.
  • → saveController
package hello.servlet.web.frontcontroller.v4.controller;

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

import java.util.HashMap;
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";
    }
}
  • memberRepository 를 불러온다.
  • 넘어온 paramMap 에서 username과 age를 꺼내 변수에 넣는다.
  • Member 객체를 만들고 저장한다.
  • 빈 model map에 member를 넣고 save-result 라는 viewName을 돌려준다.
  • viewName을 가지고 view를 resolve 하고, 모델과 함께 렌더링 한다.

List

  • URI 에서 List 를 가져온다. 파라미터가 없기 때문에 바로 빈 맵과 빈 모델이 들어간채로 controller 가 작동한다.
  • controller
package hello.servlet.web.frontcontroller.v4.controller;

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

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

public class MemberListControllerV4 implements ControllerV4 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        List<Member> members = memberRepository.findAll();

        model.put("members", members);
        return "members";
    }

}
  • memberRepository 에서 전부 가져와서 model 에 넣는다. 그러고 members 라는 viewName을 return
  • viewName을 가지고 view를 rendering 할때 모델도 같이 넣어서 제공한다.

V3와 다른점이라면, controller를 통해서 제공되는 것이 ViewName이고, Model을 따로 객체로 빼서, model에 값을 넣고, 사용한다.


V5

  • adapter를 이용한 개선
  • Controller = handler 이다.
  • 어떠한 handler를 사용할지, 핸들러를 처리할 수 있는 핸들러 어댑터를 만든다.
  • 핸들러 어댑터: 중간에 어댑터 역할을 하는 어댑터가 추가되었는데 이름이 핸들러 어댑터이다. 여기서 어댑터 역할을 해주는 덕분에 다양한 종류의 컨트롤러를 호출할 수 있다.
  • 핸들러: 컨트롤러의 이름을 더 넓은 범위인 핸들러로 변경했다. 그 이유는 이제 어댑터가 있기 때문에 꼭 컨트롤러의 개념 뿐만 아니라 어떠한 것이든 해당하는 종류의 어댑터만 있으면 다 처리할 수 있기 때문이다.

HandlerAdapter

package hello.servlet.web.frontcontroller.v5;

import hello.servlet.web.frontcontroller.ModelView;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public interface MyHandlerAdapter {
    boolean supports(Object handler); // controller 가 넘어왔을때 이 컨트롤러를 지원할수 있는지 판단
    ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;

}
  • controller 가 넘어왔을 때, 지원 가능한 controller 인지 판단하는 메서드
  • ModelView를 넘겨주는 메서드 를 구현해야 한다.

frontController

package hello.servlet.web.frontcontroller.v5;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
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 hello.servlet.web.frontcontroller.v4.controller.MemberFormControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet.web.frontcontroller.v4.controller.MemberSaveControllerV4;
import hello.servlet.web.frontcontroller.v5.adapter.ControllerV3HandlerAdapter;
import hello.servlet.web.frontcontroller.v5.adapter.ControllerV4HandlerAdapter;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

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

@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
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 initHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter());
        handlerAdapters.add(new ControllerV4HandlerAdapter());
    }

    private void initHandlerMappingMap() {
        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());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object handler = getHandler(request);

        if (handler == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);// 404 처리
            return;
        }

        MyHandlerAdapter adapter = getHandlerAdapter(handler);
        ModelView mv = adapter.handle(request, response, handler);

        // form 에 값을 넣었을때.
        String viewName = mv.getViewName(); // 논리이름 new-form

        MyView view = viewResolver(viewName);// 물리이름 new-form.jsp

        view.render(mv.getModel(), request,response); // view 호출 jsp 호출
    }

    private MyHandlerAdapter getHandlerAdapter(Object handler) {
        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");
    }

    private Object getHandler(HttpServletRequest request) {
        String requestURI = request.getRequestURI();// 요청 URI -> new-form
        return handlerMappingMap.get(requestURI); // 요청 URI에 해당하는 controller 찾기
    }
}
  • handelrMappingMap → 어떠한 컨트롤러도 들어갈 수 있게 <String, Object> 로 구현.
  • handlerAdatpers → 컨트롤러에 알맞은 adpater를 넘겨줄 수 있도록 리스트를 구현
  • 생성자를 통해 controllerV3HandlerAdapter ,v4를 넘겨준다.
  • 또한 map에 모든 URI에 대한 controller 를 넣는다.

Service

handler : request에서 넘어온 URI 에 알맞은 controller 를 찾는 메서드

getHandlerAdapter : 핸들러 어댑터를 반복문을 돌리면서 알맞은 Adapter를 찾음.

ControllerV3HandlerAdpater

package hello.servlet.web.frontcontroller.v5.adapter;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v3.ControllerV3;
import hello.servlet.web.frontcontroller.v5.MyHandlerAdapter;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

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

public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof ControllerV3);// controllerV3의 인스턴스인지 확인

    }

    @Override
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
        ControllerV3 controller = (ControllerV3) handler; // object 를 controllerV3로 캐스팅
        Map<String, String> paramMap = createParamMap(request); // 파라미터를 다 꺼내서 paramMap에 저장
        ModelView mv = controller.process(paramMap);// controller 호출
        return mv;
    }
    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))); // 파라미터에 있는 값을 다 꺼내서 paramMap에 저장
        return paramMap;
    }
}
  • support : handler와 V3를 비교해서 같으면 true 제공.
  • ModelView
    • object로 넘어왔기 때문에 casting을 먼저 한다.
    • 이후 controllerV3에 알맞게 넘겨준다.

ControllerV4HandlerAdpater

package hello.servlet.web.frontcontroller.v5.adapter;

import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.v4.ControllerV4;
import hello.servlet.web.frontcontroller.v5.MyHandlerAdapter;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

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

public class ControllerV4HandlerAdapter implements MyHandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof ControllerV4);
    }

    @Override
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
        ControllerV4 controller = (ControllerV4) handler; // 캐스팅
        Map<String, String> paramMap = createParamMap(request);// 파라미터를 다 꺼내서 paramMap에 저장 (실제로 사용하지 않음
        HashMap<String, Object> model = new HashMap<>();

        String viewName = controller.process(paramMap, model);
        ModelView mv = new ModelView(viewName);// ModelView를 생성해서 반환
        mv.setModel(model);

        return mv;
    }
    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))); // 파라미터에 있는 값을 다 꺼내서 paramMap에 저장
        return paramMap;
    }
}
  • V4도 V3과 같은 알고리즘, V4에 알맞은 데이터 형식으로 제공한다.

  • adapter.handle : 을 통해서 넘어온 controller에 알맞게 캐스팅, modelview를 제공한다.
  • 이후 modelView를 통해 jsp를 resolve 하고, 렌더링해서 view를 보여준다.