본문 바로가기

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

[Spring MVC] MVC 패턴 구현해보기 / Adapter 패턴 / 유연한 컨트롤러 구현

유연한 컨트롤러 1 - v5 

여러가지 방식의 컨트롤러를 혼용하여 사용하고 싶을 경우! 

 

어떻게 해야할까? 

 


 

어댑터 패턴 

  • 지금까지 만들었던 컨트롤러는 한 가지 방식의 컨트롤러 인터페이스만 사용할 수 있다. 
  • ControllerV3, ControllerV4 는 완전히 다른 인터페이스인데... 따라서 호환이 불가능하다는 단점이 있다! 
  • 이럴때 서로 호환이 되도록 도와주는 것이 바로 어댑터이다.
  • 어댑터 패턴을 사용하여 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 변경해보자.

 


v5 구조

 

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

달라진 점들 


Map을 선언할 때 타입을 하나의 컨트롤러로 정해주는 것이 아니라 Object 로 설정하여 모든 버전의 컨트롤러가 올 수 있도록 처리함. 

    private final Map<String, Object> handlerMappingMap = new HashMap<>();
    private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();

 

 

    @Override
    public boolean supports(Object handler) {
	    // ControllerV3 를 구현한 인스턴스가 넘어오게 되면 true 를 반환함. 다른 것이 넘어오면 false 반환
        return (handler instanceof ControllerV3); 
    }

 

 

순서는

1. 핸들러 매핑 정보에서 핸들러 조회 

2. 핸들러 어댑터 목록에서 핸들러를 처리할 수 있는 핸들러 어댑터 조회 

3. handle(handler) 핸들러 어댑터 호출

4. handler 호출

5. ModelView 반환

6. viewResolver 호출

7. MyView 반환 

8. render(model) 호출 

 

 

handler mapping 정보에서 핸들러 조회 

FrontControllerServletV5 

    public FrontControllerServletV5() {
        initHandlerMappingMap();
        initHandlerAdapters();
    }

	// handlerMappingMap 특정 url 주소를 매핑함. 
    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());
    }

    // 특정 유형은 컨트롤러를 처리할 수 있는 어댑터를 등록하는 과정 
    private void initHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter());
    }

 

  • FrontControllerServletV5의 urlpattern으로 요청이 들어오면 해당 서블릿의 기본생성자가 자동 호출된다. 
  • 여기서 요청 url을 key, Controller를 value로 지정하여 handlerMappingMap에 저장을 한다. 
  • 또한 어댑터도 함께 등록하게 됨. 
  • 따라서 특정 주소로 요청이 들어오게 될 경우 handlerMappingMap 에서 매핑 정보를 조회한다. 

 

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

    // 1. handler 호출
    Object handler = getHandler(request);

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

    // 2. handlerAdapter 가져옴.
    MyHandlerAdapter adapter = getHandlerAdapter(handler);

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

    // 3. viewResolver 호출
    String viewName = mv.getViewName();// 논리 이름 new-form
    MyView view = viewResolver(viewName);


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

 

 

 


유연한 컨트롤러2 - v5

FrontControllerServletV5 에 ControllerV4 기능도 추가하자. 

 

 

1. 요청 URL 호출 

 

http://localhost:8080/front-controller/v5/v4/members/new-form

 

url 을 타고 아래의 서블릿으로 요청이 들어오게 된다. 

 

 

2. 매핑 정보 조회 

 

여기서 매핑 정보를 찾는데 

 

  • /front-controller/v5/v4/members/new-form 이 호출된다. 
  • MemberFormControllerV4가 반환된다. 

3. 서블릿 내의 service() 메서드 호출  

  1. Service 로직이 실행 되면서 handler 객체에 MemberFormControllerV4가 반환된다. 
  2. 이 객체를 통해서 HandlerAdapter를 가져오고, 이때 ControllerV4HandlerAdapter 가 반환된다. 
  3. 그렇게 ControllerV4HandlerAdapter 에 있는 handle() 메서드가 호출된다. 

 

이때 handle 어댑터에서 뷰를 ModelView 로 변환하여 반환해주는 역할을 수행한다. 

 

 

4. 어댑터 변환 로직 (중요!) 

 

이렇게 ViewName 을  ModelView 로 변환한 뒤 반환을 해주면 

FrontController가 ModelView를 받아서 처리해준다. 

 

어댑터가 호출하는 ControllerV4는 뷰의 이름을 반환한다. 

그런데 어댑터는 뷰의 이름이 아니라 ModelView를 만들어서 반환한다. 

 

여기서 어댑터가 필요한 이유를 확실하게 깨달을 수 있다! 

 

ControllerV4 는 뷰의 이름을 반환했지만 Adapter는 이것을 ModelView 로 만들어서 형식을 맞추어 반환한다. 

 


 

이렇게 구현을 할 경우 거의 로직을 건드리지 않고 기능을 확장해나갈 수 있다는 장점이 있다. 

즉, controller 에 handle 메서드를 구현하면 되고 

FrontCotroller 의 로직을 거의 수정 할 일이 없다는 점이 장점이 될 수 있다. 

(uri를 추가해줘야 한다는 점이 아쉽지만 이건 나중에 injection 하는 방식으로 조금 편리한 방식으로 구현이 가능하다.)