Heestory

03.Spring-MVC)MVC 프레임워크 만들기 본문

개발(~국비)/Spring

03.Spring-MVC)MVC 프레임워크 만들기

까만밀가루 2022. 11. 2. 18:13

프레임워크 점진적인 발전 요약

v1 : 프론트 컨트롤러 도입 

  • 기존 구조를 최대한 유지하면서 프론트 컨트롤러를 도입

v2: VIew 분류

  • 단순 반복 되는 뷰 로직 분리

v3 : Model 추가

  • 서블릿 종속성 제거
  • 뷰 이름 중복 제거

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

  • v3과 구조 거의 비슷
  • 구현 입장에서 ModelView를 직접 생성해서 반환하지 않도록 편리한 인터페이스 제공

v5 : 유연한 컨트롤러

  • 어댑터 도입
  • 어댑터를 추가해서 프레임워크를 유연하고 확장성 있게 설계

FrontController : 서브릿 하나로 클라이언트의 요청을 받아 맞는 컨트롤러를 찾아 호출 

→ DispathcherServlet이 FrontController 패턴으로 구현되어 있다.

 

 

프론트 컨트롤러 도입 - v1


View 분리 - v2

        String viewPath = "/WEB-INF/views/new-form.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);

v1에서 모든 컨트롤러에서 뷰로 이동하는 부분의 중복이 있어 리펙토링

 

 

다른 패키지에서 쓴다면 클래스 위치 주의, MyView 클래스

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

    }
    }

 

 

Controlle의 반환 타입을 MyView로 해두어 공통 기능을 쓸 수 있게 한다 

public interface ControllerV2 {
    MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}

→ dispatcher.forward()를 직접 생성해서 호출하지 않고 MyView 객체를 생성한 후 뷰 이름만 넣고 반환하면 된다

 

return new MyView("/WEB-INF/views/new-form.jsp");

 

        MyView view = controller.process(request, response);
        view.render(request, response);

Controller의 반환 타입이 MyView이기 때문에 FrontController의 반환 타입도 MyView 객체 생성해서 반환

render에(위의 코드참고) 호출하여 forward 로직 수행

 


Model 추가 - v3

서블릿 종속성 제거
: 컨트롤러 입장에서 HttpServletRequest 등은 곡 필요하지 않다. 요청 파라미터 정보를 자바의 Map으로 대신 넘기도록 함녀 지금 구조에서는 컨트롤러가 서블릿의 기술을 몰라도 동작할 수 있다.

request 객체를 Model로 사용하는 대신에 별도의 Model 객체를 만들어서 반환하면 된다.

→ 구현 코드도 매우 단순해지고, 테스트 코드 작성이 쉽다

 

뷰 이름 중복 제거

:컨트롤러는 뷰의 논리 이름을 반환하고 실제 물리 위치의 이름은 프론트 컨트롤러에서 처리하도록 단순화 하자

→ 뷰의 폴더 위치가 함께 이동해도 프론트 컨트롤러만 고치면 된다

 

ModelView

 : Model을 직접 만들고, 추가로 View 이름가지 전달하는 객체용

public class ModelView {

    private String viewname;
    private Map<String,Object> model = new HashMap<>();

    //setter,getter 생략
    }

뷰의 이름과 뷰를 런더링 할 때 필요한 model 객체를 가지고 있다.

 

ControllerV3 

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

서블릿 구조 사용하지 않음

HttpServletRequest가 제공하는 파라미터는 프론트 컨트롤러가 paramMap에 담아서 호출

응답 결과로 뷰 이름과 뷰에 전달할 Model데이터를 포함하는 ModelView객체를 반환

 

return new ModelView("new-form");

view의 논리적 이름만 사용하고 , 실제 물리적이름은 프론트 컨트롤러에서 처리

 

이후 프론트 컨트롤러에서 뷰 리졸버를 이용하여 반환한 논리 뷰 이름을 실제 물리 뷰 경로로 변경한다.

또한 JSP는 request.setAttribute(), request.getAttribute()를 이용하기 때문에 model에 담아둔 내용을 변경한다.

 


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

ModelView 객체를 생성하고 반환해야 하는 부분이 번거로워 ModelView를 반환하지 않고 viewName만 반환하도록 

컨트롤러에서  model을 파라미터로 전달하기 때문에 뷰 이름만 반환

//ControllerV4 
public interface ControllerV4 {

    /**
     *
     * @param paramMap
     * @param model
     * @return
     */
    String process(Map<String,String> paramMap, Map<String, Object> model);
}

 

    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        //v3와 비교했을때 modelview가 필요 없음
        return "new-form";
    }

 

이후 프론트 컨트롤러에선 paramMap과 model 객체를 생성한다.

 


유연한 컨트롤러1 - v5

어댑터 패턴 

: ControllerV3, ControllerV4 는 완전히 다른 인터페이스이다. 이런 호환이 필요할 대 어댑터

  • 핸들러 어댑터 : 어댑터, 다양한 종류의 컨트롤러 호출
  • 핸들러 : 컴트롤러의 이름을 더 넓은 범위의 핸들러로 변경
//어댑터를 이렇게 구현해야 한다는 어댑터용 인터페이스
public interface MyHandlerAdapter {

    //어댑터가 해당 컨트롤러를 처리할 수 있는지 판단하는 메서드
    boolean supports(Object handler);
    
    //실제 컨트롤러를 호출하고 그 결과로 ModelVeiw를 반환
    ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}

 

 

 

public class FrontControllerServletV5 extends HttpServlet {

//    private Map<String, ControllerV4> controllerMap = new HashMap<>();
    private final Map<String, Object> handlerMappingMap = new HashMap<>(); //핸들러매핑 정보 담겨있음, Object - Controller 종류 무관하게 담기 위해
    private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>(); //List 어댑터가 여러개 담겨있어 여기에 정보 담는다

    //생성자는 핸들러 매핑과 어댑터를 초기화(등록)한다
    public FrontControllerServletV5() {
        initHandlerMappingMap(); //핸들러 매핑 초기화
        initHandlerAdapters(); //어댑터 초기화
    }

    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());
    }
    
    private void initHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter()); 
        handlerAdapters.add(new ControllerV4HandlerAdapter()); 
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //URI를 통해 핸들러를 찾는다. 핸들러 매핑
        Object handler = getHandler(request); //핸들러 찾아와

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

        //어댑터 찾기, 핸들러를 처리할 수 있는 어댑터 조회
        MyHandlerAdapter adapter = getHandlerAdapter(handler); //어댑터 찾아와

        //어댑터 호출
        ModelView mv = adapter.handle(request, response, handler);

        String viewName = mv.getViewname();
        MyView view = veiwResolver(viewName);

        view.render(mv.getModel(),request,response);
    }

 

'개발(~국비) > Spring' 카테고리의 다른 글

05.Spring-MVC)기본 기능  (0) 2022.11.05
04.Spring-MVC)MVC 구조의 이해  (0) 2022.11.04
02.Spring MVC)서블릿,JSP,MVC 패턴  (0) 2022.11.01
01.Spring MVC)서블릿  (0) 2022.10.28
09. 스프링)빈 스코프  (0) 2022.10.27