개발자로 후회없는 삶 살기

spring PART.유연한 컨트롤러 본문

[백엔드]/[spring | 학습기록]

spring PART.유연한 컨트롤러

몽이장쥰 2023. 4. 12. 00:25

서론

※ 이 포스트는 다음 강의의 학습이 목표임을 밝힙니다.

https://www.inflearn.com/roadmaps/373

 

우아한형제들 최연소 기술이사 김영한의 스프링 완전 정복 - 인프런 | 로드맵

Spring, MVC 스킬을 학습할 수 있는 개발 · 프로그래밍 로드맵을 인프런에서 만나보세요.

www.inflearn.com

 

본론

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

이전에 만든 V3는 서블릿 종속성, 경로 중복을 제거한 잘 설계한 프레임워크입니다. 사용자인 개발자는 컨트롤러 인터페이스에 맞게 컨트롤러, jsp만 설계하면 모델 뷰, myview, 프론트가 상호작용하면서 개발을 할 수 있습니다. 하지만 막 개발자 입장에서는 항상 모델 뷰 만들어서 반환하고 막 번거롭습니다. 개발자가 사용하기 매우 편리하게 만들어보겠습니다.

 

- V4 구조

구조적으로는 v3와 같다. v3가 구조적으로는 프론트 컨트롤러 패턴의 완성입니다. v4는 불편한 모델 뷰 반환을 하지 않고 viewName만 반환합니다. 구조를 그대로 살리면서 해보겠습니다.

 

 

컨트롤러 인터페이스를 보면 이전에 paramMap만 넘겼는데 이제는 모델도 같이 넘깁니다. 이전에는 모델 뷰를 생성하면서 거기에 있는 모델을 가지고 쓰고 그랬습니다.

 

v4에는 그게 아니라 프론트가 모델까지 다 만들어서 각 컨트에게 넘겨주는 것입니다. (프론트에 할 일 추가) 개발자는 컨트에서 넘겨 받은 모델에 값을 put해서 쓰면 됩니다. 그리고 컨트롤러의 반환은 Str로 viewName만 하면 됩니다. 실제 개발자가 신경써야하는 구현코드는 굉장히 심플하게 되는 것입니다.

 

 

- 구현 시작

public interface ControllerV4 {
    String process (Map<String, String> paramMap, Map<String, Object> model);
}

패키지 v4를 만들고 인터페이스를 만듭니다. 원래 process 메서드가 질의 파라미터인 paramMap만 매개변수로 받고 모델 뷰를 반환했었는데 모델도 받고 string을 반환합니다.

 

-> 컨트롤러 구현하기

public class MemberFromControllerV4 implements ControllerV4 {
    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        return "new-form";
    }
}

form 컨트롤러를 구현합니다. return으로 논리 경로만 넣으면 됩니다. v3랑 비교해보면 모델 뷰가 필요가 없습니다. 모델도 프론트 컨트롤러에서 만들어서 넘겨줄 것이므로 만들 필요가 없고 모델 뷰도 필요가 없습니다. 모양을 보면 RequestParam, Model을 인자로 넘겨 받는 스프링 맵핑 함수와 거의 비슷합니다.

 

 

public class MemberSaveControllerV4 implements ControllerV4 {
    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("membder", member);
        return "save-result";
    }
}

save를 구현합니다. 레포에 저장하는 것은 똑같고 모델 뷰를 논리 경로로 생성하고 모델 뷰의 모델에 put으로 멤버 넣고 이런게 다 필요가 없습니다. 그냥 매개변수로 넘어온 모델에 put하면 됩니다. 반환을 경로로 하면 됩니다.

 

public class MemberListControllerV4 implements ControllerV4 {
    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";
    }
}

목록 컨트롤러도 구현합니다. 얘도 모델에 put하고 return만하면 됩니다. 점점 스프링과 비슷해집니다. 이제 프론트를 만듭니다.

 

-> 프론트 컨트롤러 구현

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String requestURI = req.getRequestURI();

    ControllerV4 controller = controllerMap.get(requestURI);
    if(controller == null) {
        resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
        return;
    }
    Map<String, String> paramMap = createParamMap(req);
    Map<String, Object> model = new HashMap<>();

    String viewName = controller.process(paramMap, model);
    MyView myView = viewResolver(viewName);
    myView.render(model, req, resp);
}

맵핑 정보는 같습니다. 구조는 기존 것이 완성이 됐기에 바꿀게 별로 없습니다. 근데 process에 모델을 넘겨야합니다. 텅빈 모델입니다. 컨트롤러에서 이 모델에 값을 넣습니다. 그러니 프론트에서 그 모델을 쓰면 되는 것입니다. process의 반환은 str viewname입니다. 

 

> 그러니 이제 모델 뷰에서 받던 viewname가 필요없습니다. > render의 모델이 기존에는 모델 뷰에서 모델 꺼냈는데 이제 프론트에서 제공하는 모델 쓰면 됩니다. 우리가 만든 것이 강사님께서 계속 프레임워크라는 것을 강조하십니다. 보면 프론트 컨트롤러가 스프링에도 있습니다. 스프링의 맵핑 메서드에서 모델을 받아서 사용하는데 그걸 주는 것이 프론트 컨트롤러입니다. 사실 스프링도 제가 만든 방식으로 구조가 되어있는 것입니다. 스프링도 모델을 개발자가 사용하기 쉽게 프론트에서 넣어주고 프론트 컨트롤러에서 사용하는 것입니다.

 

-> 정리

컨트롤러를 구현하는 개발자 입장에서 엄청 편해졌습니다. 모델도 컨트에 넘어가니깐 개발자가 "이거 쓰면 되겠구나"도 바로 알 수 있고 말입니다.

> 이번 버전의 컨트롤러는 매우 실용적입니다. 그런데 개발자 입장에서는 진짜 군더더기 없는 코드를 작성할 수 있습니다. 기존에 탄탄한 구조가 있기에 이렇게 점진적으로 발전 할 수 있었습니다.

 

- 유연한 컨트롤러 v5

지금까지 만든 각 컨트롤러는 다 인터페이스를 구현하기에 모양이 정해져있습니다. 다른 모양으로 할 수가 없습니다. 인터페이스로 제약하는 것의 장점이자 단점입니다. 이제 하나의 프론트에서 어떤 컨트롤러든 호출할 수 있도록 해보겠습니다. 즉 컨트롤러 v3 버전으로 개발하고 싶으면 할 수 있도록 해보겠습니다. 지금은 v4밖에 안됩니다.

 

- 어댑터 패턴

지금까지 개발한 프론트는 한 가지 인터페이스만 가능합니다. v3는 110v v4는 220v인 전기 콘센트같은 것입니다. 이럴 때 쓰는게 어댑터 패턴으로 여러가지 것들을 바꿀 수 있습니다. 개발에서 뭔가가 안 맞을 때 중간에 뭘 껴서 맞추는 것을 어댑터 패턴이라고 합니다. 어댑터를 사용해서 프론트 컨트가 다양한 방식의 컨트를 처리할 수 있도록 해보겠습니다. (개발자가 v3 방식 form 컨트를 쓸 수도 있고 v4 방식 form 컨트를 쓸 수도 있게 해보겠습니다.)

 

-> 그림

핸들러가 컨트롤러이고 핸들러 어댑터를 찾아옵니다. "이거 110용 어댑터, 이건 220용 어댑터" 컨트롤러 v3를 처리할 수 있는, v4를 할 수 있는 어댑터를 찾아옵니다. 만약 핸들러 맵핑 정보가 v4이면 v4를 할 수 있는 어댑터를 가져와서 어댑터가 대신 호출했습니다. 기존에는 프론트 컨트에서 핸들러를 바로 호출했습니다. 그런데 이젠 그게 안됩니다. 중간에 핸들러 어댑터를 통해서 핸들러를 호출해야합니다. 그래서 이름이 핸들러 어댑터입니다.

 

+ 프론트도 자기가 직접 컨트를 호출할 수 있는게 아니고 어댑터를 통해서 호출해야합니다. > 어댑터를 호출할 때 파라미터로 handler라고 실행할 컨트롤러를 줍니다. 그러면 어댑터가 대신 호출해주고 프론트한테 모델 뷰를 반환해줍니다. 나머지 프로세스는 같습니다.

 

-> 용어

1) 핸들러 어댑터 : 중간에 추가되는 어댑터, 여기서 어댑터 역할을 해주는 덕분에 다양한 종류의 컨트를 호출 가능합니다.

2) 핸들러 : 컨트라고 보면됩니다. 근데 이름을 좀 더 넓은 범위의 핸들러로 변경했습니다. 왜냐면 과거에는 컨트를 처리한다고 개발을 했었는데 이제 어댑터라는게 있어서 어떠한 종류든 다 처리할 수 있습니다.

 

- 핸들러 어댑터 인터페이스

2가지 기능이 있습니다.

 

1) 서포트 메서드

요청이 오면 프론트 컨트가 핸들러 맵핑 정보에서 (여기서는 컨트롤러가 핸들러입니다.) 핸들러(컨트롤러)를 하나 찾아옵니다.(원래도 컨트롤러를 하나 찾아왔습니다. 똑같습니다.) 그리고 핸들러 어댑터 목록을 찾을 때 방금 찾아온 핸들러가 v3면 v3를 처리할 수 있는 어댑터를 꺼내야합니다. 그때 사용하는게 서포트입니다. 할 수 있으면 true, 아니면 false입니다. 어떠한 v의 핸들러도 검사할 수 있어야하니 Obj를 매개 타입으로 합니다.

 

2) 핸들 메서드

어댑터가 핸들러를 대신 호출한다고 했습니다. 그리고 반환을 할 때 모델 뷰를 맞춰서 반환해줍니다. 어댑터는 실제 컨트롤러를 호출해야하고 그 결과로 모델 뷰를 반환해야합니다. 이전에는 프론트 컨트가 실제 컨트를 직접 호출했지만 이제는 어댑터를 통해서 각 컨트를 호출합니다.

 

+ 맵핑 정보로 컨트를 가져오고 컨트에 맞는 어댑터를 어댑터 목록에서 가져오고 그 어댑터가 대신 컨트롤러를 호출해줍니다.

 

-> 어댑터 인터페이스 작성

public interface MyHandlerAdapter {
    boolean supports(Object handler);
    ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}

v5에 어댑터 인터를 만듭니다. 서포트 메서드를 만들고 인자는 핸들러입니다. 핸들러가 넘어왔을 때 "내가 이 핸들러를 지원할 수 있어!" 이거입니. 이 인터페이스로 구현된 어댑터들은 자기가 이 핸들러를 지원할 수 있는 건지 판단하고 할 수 있으면 핸들러를 대신 호출해 줄 것입니다.

 

> 핸들 메서드도 만듭니다. 서블릿의 서비스와 같은 매개변수를 가지고 추가로 핸들러도 인자로 받습니다. 실제 컨트를 호출해주는 메서드이기 때문에 서블릿과 같은 매개를 합니다. 이를 인터로 구현한 이유가 있습니다. 얘를 구현한 것들이 실제 어댑터로 컨트롤러 v3, v4, v1을 지원하는 어댑터를 이 인터를 구현하여 할 것입니다.

 

-> v3 컨트롤러 어댑터 구현

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

    @Override
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
        ControllerV3 controller = (ControllerV3) handler;

        Map<String, String> paramMap = createParamMap(request);
        ModelView modelView = controller.process(paramMap);
        return modelView;
    }

    private static Map<String, String> createParamMap(HttpServletRequest req) {
        Map<String, String> paramMap = new HashMap<>();
        req.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paramMap.put(paramName, req.getParameter(paramName)));
        return paramMap;
    }
}

어댑터 패키지를 만들고 (컨트롤러를 만들 때처럼) v3를 지원하는 어댑터를 만듭니다. 아까 만든 인터의 메서드를 구현합니다. > 서포트 메서드는 핸들러가 인자로 넘어옵니다. "그 넘어온 핸들러를 지원할 수 있어? 없어?"를 묻는 것입니다. instanceof를 써서 물어보고 t, f를 반환합니다.

 

> 핸들 메서드는 실제 컨트를 프론트 대신에 호출하는 것입니다. 받은 핸들러를 v3(맞는 버전으로)로 캐스팅하고 레퍼 이름은 컨트롤러로 합니다. 프론트 컨트에서 거를 거 거르고 서포트도 호출하고 핸들도 호출할 것입니다. 걸러진 애를 찾아서 핸들을 호출할 것 입니다.

 

> 컨트를 대신 호출해주는 것이 예전에 프론트에서 process를 호출하는 것처럼 그냥 메서드를 대신 호출해주는 것입니다. 핸들 메서드에 v3 버전 프론트 컨트롤러에서 한 것을 대신 얘가 해주는 것입니다. 아까 위에서 프론트가 각 컨트를 호출하는 것이 아니라 어댑터가 호출해준다고 했는데 그게 이것입니다.

 

어댑터의 역할은 핸들러를 호출해주고 그 반환 타입은 모델 뷰입니다. v3는 모델 뷰를 반환하기에 딱 됩니다. 근데 v4는 str를 반환했기에 거기선 어댑터의 로직이 달라집니다.

 

-> 프론트 컨트롤러

기존
이후

기존과 조금 차이가 있습니다. 기존엔 컨트롤러 맵핑 정보를 위해 만들었는데 이제는 핸들로 맵핑 map을 만들었습니다. 기존에는 여기에 controllerV4 이렇게만 되는데 이제는 다 들어가야하니 Obj를 넣습니다.

 

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

public FrontControllerServletV5() {
    handlerMappingMap.put("/front-controller/v3/members/new-form", new MemberFromControllerV3());
    handlerMappingMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
    handlerMappingMap.put("/front-controller/v3/members", new MemberListControllerV3());

    handlerAdapters.add(new ControllerV3HandlerAdapter());
}

> 그리고 list로 어댑터가 여러개 담겨있고 그 중에서 버전에 맞는 하나를 찾을 것을 만듭니다. > 이 두개에 값을 넣스비다. 생성자에서 기존처럼 맵핑 정보를 넣습니다. 이전에는 v3 프론트 컨트롤러의 생성자에서 v3에 해당하는 컨트를 호출하기 위한 맵핑 정보만을 넣었습니다. 근데 이제는 v4를 포함할 것이고 v3 먼저 지원하는 것을 만들 것이라서 핸들러 맵핑 정보에 v3를 넣지만 나중에 다른 것들도 다 넣을 것입니다. 이게 obj라서 다 됩니다.

 

> 핸들러 어댑터에 list니깐 add로 v3 어댑터를 넣습니다. 둘 다 메서드 추출합니다. 지금은 v3만 있으니 이런데 나중에 v4를 넣으면 맵핑 정보도, 어댑터도 확 들어날 것입니다.

 

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

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

    MyHandlerAdapter adapter = getHandlerAdapter(handler);
    ModelView modelView = adapter.handle(req, resp, handler);

    String viewName = modelView.getViewName();
    MyView myView = viewResolver(viewName);

    myView.render(modelView.getModel(), req, resp);
}

private Object getHandler(HttpServletRequest req) {
    String requestURI = req.getRequestURI();
    return handlerMappingMap.get(requestURI);
}

private MyHandlerAdapter getHandlerAdapter(Object handler) {
    for (MyHandlerAdapter handlerAdapter : handlerAdapters) {
        if(handlerAdapter.supports(handler)) {
            return handlerAdapter;
        }
    }
    throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler);
}

서비스를 구현합니다. 동일하게 URI로 원하는 것을 꺼내는 코드를 작성해야합니다. 핸들러 맵핑 맵에서 원하는 URL의 핸들러를 가져오도록하고 이것도 추출합니다.

> 그 다음에는 그림을 보자 지금 요청이오면 프론트에서 핸들러 맵핑 정보에서 핸들러 조회하는 것까진 했습니다. 이 핸들러를 v3냐 v4냐 처리할 수 있는 어댑터를 찾기 위해 어댑터 목록에서 찾아와야한다. 그것을 작성해보겠습니다.

 

private MyHandlerAdapter getHandlerAdapter(Object handler) {
    for (MyHandlerAdapter handlerAdapter : handlerAdapters) {
        if(handlerAdapter.supports(handler)) {
            return handlerAdapter;
        }
    }
    throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler);
}

단순하게 handlerAdapters를 for each문으로 도는 것입니다. 어댑터 목록을 돌려서 찾아야합니다. 이때 서포트로 이 핸들러를 지원하는가를 봅니다. 지원하면 그 어댑터를 고릅니다. 이 어댑터가 선택이 된 것입니다. 찾아봤는데 없으면 예외를 터뜨립니다. 없는 것이라고 합니다.

 

> 지금 프론트 코드를 보면은 먼저 원래와 같이 핸들러를(각 컨트) 찾아오고 그에 맞는 핸들러 어댑터 찾아왔습니다. 그림과 똑같이 만들었습니다. 컨트도 찾았고 그 컨트를 대신 호출할 어댑터도 찾았습니다.

> 이제 어댑터가 핸들 메서드로 호출합니다. 얘가 모델 뷰를 반환합니다. 그걸 그대로 쓰면 됩니다. 

 

-> 순서

그림을 보면서 순서를 보겠습니다.

1) 요청이 오면 핸들러 맵핑 정보를 뒤집니다. 이게 컨트 맵핑 정보랑 아예 똑같습니다. 근데 MAP을 obj로 둬서 모든 컨트를 있도록했습니다. 핸들을 가져옵니다.

2) 이 핸들로 핸들러 어댑터 목록을 뒤져서 이 핸들을 처리할 수 있는 어댑터를 for문으로 찾아옵니다.

3) 핸들러 어댑터의 핸들 메서드로 프론트 대신에 핸들러(컨트)를 호출합니다. 핸들러 어댑터가 process를 호출해서 실제 컨트를 호출합니다. 모델 뷰를 반환합니다.

4) 모델 뷰 그대로 써서 뷰 리졸버쓰고 쭉 해서 jsp 랜더를 합니다. myview가 전담해서 뷰를 호출합니다.

 

 

-> 실행 오류가 뜨면

디버거를 봅니다. 해보면 404가 뜹니다. 디버거를 보면 요청이 Received [GET /front-controller/v5/v3/members/new-form HTTP/1.1 이렇게 왔습니다. 이를 보고 생각을 합니다.

 

handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFromControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());

디버거를 보면 이 링크로 핸들러 맵핑 정보에서 맞는 핸들러를 못찾은 것입니다. 가보면 v5 v3로 되어야합니다. 근데 맵핑 정보를 init에서 초기화한 것 보면 v3로 되어있기에 v5/*에 맞춰서 v5를 추가합니다.

 

-> 정리

유연한 컨트롤러를 만들 것입니다. 그래서 어댑터를 끼워넣었습니다. 어찌보면 어댑터들이 mv만 반환하게 하면 됩니다. v4를 쓸 거면 v4를 맵핑 정보를 넣고 핸들러 어댑터 v4를 구현해서 adapters에 넣으면 되겠습니다. v4 어댑터도 mv를 반환하도록하면 똑같겠습니다!

 

 

- v5 컨트롤러에 v4 기능 추가

private void initHandlerMappingMap() {
    handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFromControllerV3());
    handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
    handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
}
private void initHandlerMappingMap() {
    handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFromControllerV3());
    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 MemberFromControllerV4());
    handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
    handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
}

맵핑 정보에 v4 컨트를 넣습니다. 핸들러 어댑터 v4를 구현합니다.

 

 

-> 컨트롤러 v4 어댑터 구현

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);
        Map<String, Object> model = new HashMap<>();

        String viewName = controller.process(paramMap, model);
        ModelView mv = new ModelView(viewName);
        mv.setModel(model);

        return mv;
    }

서포트는 기존과 똑같습니다. 핸들 메서드도 비슷합니다. 캐스팅하고 하면 되는데 이 컨트롤러는 v3와 다릅니다.  컨트롤러 process가 paramMap과 모델을 둘 다 받습니다. 이건 v4 프론트에서 코드 가져오면 됩니다. 왜냐면 기존에 프론트 v4에서 처리하던 것을 이 어댑터가 대신하는 것이기 때문입니다.

> 얘가 viewName을 반환합니다. 드디어 어댑터 다운 역할이 나옵니다! 모델 뷰를 어댑터에서 생성합니다. 어댑터에서 하는게 110v를 220v로 바꿔주는 어댑터다운 행동입니다. 모델 뷰는 모델과 viewname이 두개 필요합니다. 모델은 process에서 save 컨트가 필요한 데이터를 다 담아 놓을 것이니 그냥 mv.setModel하면 됩니다. 이러면 끝납니다.

 

-> 강사님께서 내가 성장했다는 다형성

1. 각 컨트롤러를 인터페이스로 개발하고 구현체로 구현하였습니다.
2. 어댑터 패턴을 사용하여 서로 다른 모양의 인터페이스를 구현한 구현체를 하나의 프론트 컨트롤러에서 사용했습니다.

 

3. 계속 프레임워크, 어노테이션 방식의 컨트롤러를 사용할 수 있다고 하시는 것은 개발자는 사실 컨트롤러만 개발합니다. 프론트랑 컨트롤러 인터페이스는 프레임워크에 되어있는 그대로 사용하는 것입니다. 그런데 스프링에서 컨트롤러를 구현할 때 어노테이션 방식의 컨트롤러를 썼었습니다. 그게 v3, v4처럼 어노테이션 방식의 컨트롤러 인터페이스가 있는 것이고 이를 개발자가 구현해서 쓰는 것입니다. 사실 어노테이션 방식 말고 v3, v4 방식도 다 쓸 수 있습니다. 근데 어노테이션 방식이 쉬워서 많이 쓰는 것이고 개발자가 프로젝트를 할 때 어떤 컨트롤러는 어노 방식으로 하고 어떤 컨트롤러는 v3로 해도 하나의 프론트에서 처리해줍니다. 그게 스프링도 어댑터 패턴을 도입한 프론트 컨트롤러 패턴을 쓰기 때문입니다.

4. 지금은 프론트 컨트롤러에서 사용할 컨트의 맵핑고 핸들러 목록을 저장하여 여기만 추가하면 모든 컨트와 어댑터를 쓸 수 있습니다. 이것을 주입으로 바꾸면 진짜 클라 코드는 전혀 안 바꾸고 원해는 버전의 컨트로러를 쓸 수 있을 것입니다. 

> 왜 그런지 생각해보면 프론트 컨트롤러가 스프링에서 어떻게 설계되어있을까 생각해보면 내가 어노테이션 방식 컨트롤러를 선택하면 프론트의 init에 막 add하고 put하는 것이 아닌 주입되는 방식으로 되어있을 것 같습니다. 그러면 의존관계를 분리하며 OCP를 바꿔가며 개발할 수 있습니다. 스프링 MVC가 이 모든게 되어있습니다. 이 구조를 거의 바꾸지 않고 @Controller를 처리할 수 있는 핸들러 어댑터를 끼워넣으면서 그냥 해줍니다.

Comments