위의 코드를 보면 client(CType port) 는 CType을 인자로 받는다. 여기서 만약 8핀으로 수정하고 싶으면 client()를 수정해야한다.
👉 그럼 왜 `Adapter`를 사용할까?
현재 EightPin(Adaptee)는 직접 작성한 클래스라 수정이 가능하지만 만약 라이브러리로 제공되는 클래스라면 수정이 불가능할 것이다. 그렇다면 CType클래스의 코드를 변경할 수도 있지만 이 경우에도 기존에 CType을 많은 곳에서 사용할 수록 수정 작업이 쉽지 않을뿐 아니라 오류 발생율도 증가하게 될 것이다.
예제2
다음 예제는 Spring Security에서 제공하는 UserDetails에 관한 코드이다.
// 해당 핸들러를 처리할 수 있는 HandlerAdapter를 찾아온다.// Determine handler adapter for the current request.HandlerAdapter ha =getHandlerAdapter(mappedHandler.getHandler());// ..(생략)// 핸들러를 찾아오면 요청을 처리한다. 처리결과로 model and view를 반환한다.// Actually invoke the handler.mv =ha.handle(processedRequest, response,mappedHandler.getHandler());
getHandlerAdapter()
핸들러는 다양한 형태이기 때문에 Object 타입으로 받아온다.
핸들러를 처리할 수 있는 HandlerAdapter를 찾아서 반환한다.
protectedHandlerAdaptergetHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters!=null) {for (HandlerAdapter adapter :this.handlerAdapters) {if (adapter.supports(handler)) {return adapter; } } }thrownewServletException("No adapter for handler ["+ handler +"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}
어떤 핸들러를 사용하느냐에 따라 각기 다른 핸들러 어댑터를 사용하게 된다.
핸들러 어댑터는 간단한 인터페이스만 구현해주면 된다.
publicinterfaceHandlerAdapter { /** * Given a handler instance, return whether or not this {@code HandlerAdapter} * can support it. Typical HandlerAdapters will base the decision on the handler * type. HandlerAdapters will usually only support one handler type each. * <p>A typical implementation: * <p>{@code * return (handler instanceof MyHandler); * } * @param handler the handler object to check * @return whether or not this object can use the given handler */booleansupports(Object handler); /** * Use the given handler to handle this request. * The workflow that is required may vary widely. * @param request current HTTP request * @param response current HTTP response * @param handler the handler to use. This object must have previously been passed * to the {@code supports} method of this interface, which must have * returned {@code true}. * @throwsException in case of errors * @return a ModelAndView object with the name of the view and the required * model data, or {@code null} if the request has been handled directly */@NullableModelAndViewhandle(HttpServletRequest request,HttpServletResponse response,Object handler) throwsException; /** * Same contract as for HttpServlet's {@code getLastModified} method. * Can simply return -1 if there's no support in the handler class. * @param request current HTTP request * @param handler the handler to use * @return the lastModified value for the given handler * @see javax.servlet.http.HttpServlet#getLastModified * @see org.springframework.web.servlet.mvc.LastModified#getLastModified */longgetLastModified(HttpServletRequest request,Object handler);
핸들러 어댑터는 요청을 처리하는 방법을 구현해주면 된다.
그 중 가장 많이 사용하는 것이 RequestMappingHandlerAdapter이다.
원한다면 직접만들어서 구현할 수 있다.
스프링은 Adapter에 해당하는 인터페이스를 제공해주는 것이다.
HttpServletRequest와 HttpServletResponse를 받아서 ModelAndView를 반환해주는 어댑터에 대한 인터페이스를 정의한 것이 HandlerAdapter이다.
왜 이런 어댑터 인터페이스가 필요했을까?
다양한 형태의 핸들러가 있고, 각기 다른 형태에 따라 각각 다르게 처리해야하기 때문에, 다르게 처리해야하는 모든 핸들러가 스프링MVC에 들어있고, 다양한 형태의 핸들러를 다 지원할 수 있게(확장에 열려있게) 해주기 위해 스프링 MVC가 고안해놓은 인터페이스이다.
어댑터 패턴을 이해한 후에 SpringMVC의 DispatcherServlet 구현 코드를 읽어보고 doDispatch의 동작 원리를 공부해보면 좋을 것 같다!
레거시 시스템을 원하는 인터페이스로 사용 가능하게 할 수 있고, 어댑터 객체에서 적절히 구현 후 적용한다면 단순한 wrapping 이상의 효과를 볼 수 있을 것 같다.
추가로 설명하자면
선언한 컨트롤러 클래스내의 메서드를 실행하여 응답을 하는데 이때 반환 값이 String/ModelAndView 로 다양한 결과값을 반환할 수 있습니다. 하지만 Servlet에서는 무조건 ModelAndView를 받아서 이를 사용하도록 구현되었기 때문에 두 사이를 연결하기 위해 HandlerAdpater가 필요합니다.
대표적인 HandlerAdpater로 RequestMappingHandlerAdpater / HttpRequestHadlerAdapter / SimpleControllerAdapter가 존재고 이외에도 HandlerFucntionAdapter, SimpleServletHandlerAdpater가 존재합니다.
1. RequestMappingHandlerAdpater
첫번째로 **RequestMappingHandlerAdpater**는 AbstractHandlerMethodAdpater를 상속한 구현체로 @Annotation방식의 핸들러에 사용이 됩니다.