본문 바로가기
Develop/Java

[MVC] 어댑터 추가

by 연로그 2021. 5. 19.
반응형

110v 코드에는 220v 코드를 꽂을 수 없다.

어댑터를 사용하면 220v 코드도 110v에서 사용할 수 있도록 해준다.

이처럼 코드에서도 호환을 도와주는 어댑터를 추가할 수 있다.

자세한 설명은 '어댑터 패턴'에 대해 공부해보길 바란다.

 

V3 Controller 활용하기

먼저는 v3에서 만든 컨트롤러를 사용할 수 있도록 하겠다.

v3 코드: https://yeonyeon.tistory.com/105 

 

[MVC] Model 분리

현재 코드의 문제점 컨트롤러는 매번 사용하지도 않는 HttpServletRequest, HttpServletResponse를 받는다. "/WEB-INF/views/new-form.jsp" 같은 경로에서 "/WEB-INF/views" 같이 경로가 반복된다. 현재 서블릿에..

yeonyeon.tistory.com

 

프로젝트 구조

 

 

MyHandlerAdapter

public interface MyHandlerAdapter {
	boolean supports(Object handler);
	ModelView handle(HttpServletRequest req, HttpServletResponse res, Object handler) throws ServletException, IOException;
}
  • supports와 handle 2가지 생성
  • 이후 supports에서 Controller 타입을 검증하고 handle에서 로직을 수행할 것

 

ControllerV3HandlerAdapter

public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
	@Override
	public boolean supports(Object handler) {
		return (handler instanceof ControllerV3);
	}
	
	@Override
	public ModelView handle(HttpServletRequest req, HttpServletResponse res, Object handler) throws ServletException, IOException {
		ControllerV3 controller = (ControllerV3) handler; 
        
		Map<String, String> paramMap = createParamMap(req);
		ModelView mv = controller.process(paramMap);
		return mv;
	}

	private 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;
	}
}
  • handler instanceof ControllerV3: 파라미터로 받은 handler의 인스턴스 유형이 ControllerV3과 동일 여부를 boolean 타입으로 반환
  • (ControllerV3) handler: supports()에서 파라미터가 ControllerV3임을 검증하고 수행하기 때문에 ControllerV3으로 매핑해도 상관없다.

 

FrontControllerServletV5

@WebServlet(name = "frontControllerServletV5", urlPatterns="/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
	private Map<String, Object> handlerMappingMap = new HashMap<>();
	private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();
	
	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());
	}
	
	private void initHandlerAdapters() {
		handlerAdapters.add(new ControllerV3HandlerAdapter());
	}
	
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		Object handler = getHandler(req);
		
		if(handler == null) {
			res.setStatus(HttpServletResponse.SC_NOT_FOUND);	//404
			return;
		}
		
		MyHandlerAdapter adapter = getHandlerAdapter(handler);
		ModelView mv = adapter.handle(req, res, handler);
		
		String viewName = mv.getViewName();
		MyView view = viewResolver(viewName);
		view.render(mv.getModel(), req, res);
	}
	
	private Object getHandler(HttpServletRequest req) {
		String reqURI = req.getRequestURI();
		return handlerMappingMap.get(reqURI);
	}
	
	private MyHandlerAdapter getHandlerAdapter (Object handler) {
		for(MyHandlerAdapter adapter : handlerAdapters ) {
			if(adapter.supports(handler)) {
				return adapter;
			}
		}
		throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다.");
	}
	
	private MyView viewResolver(String viewName) {
		return new MyView("/WEB-INF/views/" + viewName + ".jsp");
	}
}
  • FrontControllerServletV5(): handlerMappingMap과 handlerAdapters 값 초기화
  • getHandlerAdapter(): 파라미터로 받은 값과 일치하는 HandlerAdapter 찾기. 존재하지 않을 경우 예외 발생
  • service():
  1. req에서 어떤 컨트롤러를 호출했는지 handler에 저장
  2. handler가 발견되지 않으면 404 에러 처리
  3. adapter 변수에 MyHandlerAdapter 저장
  4. mv 변수에 handle()의 반환값(Controller의 process() 실행 결과) 저장
  5. view 이름을 가져와 MyView 객체 생성
  6. 생성한 MyView의 render() 실행

 

v3 하나만 추가한 결과...

아직까지는 이렇게 할 필요가 있을까? v4가 훨씬 낫지 않나? 라는 생각이 든다.

v4를 추가하면 이 패턴의 진면목을 볼 수 있다하니 이어서 공부해보겠다.

 

V4 Controller 활용하기

ControllerV4HandlerAdapter

public class ControllerV4HandlerAdapter implements MyHandlerAdapter {
	@Override
	public boolean supports(Object handler) {
		return (handler instanceof ControllerV4);
	}
	
	@Override
	public ModelView handle(HttpServletRequest req, HttpServletResponse res, Object handler) throws ServletException, IOException {
		ControllerV4 controller = (ControllerV4) handler;
		
		Map<String, String> paramMap = createParamMap(req);
		Map<String, Object> model = new HashMap<>();
		
		String viewName = controller.process(paramMap, model);
		ModelView mv = new ModelView(viewName);
		mv.setModel(model);
		
		return mv;
	}

	private 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;
	}
}
  • Controller 특성상 handle() 메소드 내의 로직은 다소 달라졌지만 대략적인 흐름은 ControllerV3HandlerAdapter와 동일

 

FrontControllerServletV5

@WebServlet(name = "frontControllerServletV5", urlPatterns="/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
	...
	private void initHandlerMappingMap() {
		// v3
		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());
		
		// v4
		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());
	}
	...
}

 

  • 설정하는 부분인 initHandlerMappingMap()과 initHandlerAdapters() 두 메소드만 수정

 

handler 관련한 로직을 미리 짜두니 새로운 컨트롤러를 추가할때는 수정이 거의 없다.

새로운 컨트롤러에 대한 어댑터 파일 하나 생성하고, Front Controller에서 설정만 추가해주었다.

만약 설정을 Front Controller가 아닌 다른 파일에 빼둔다면 Front Controller를 건드릴 필요도 없게 된다.

 

어댑터를 도입함으로서 여러 버전의 Controller의 호환이 가능하게 되었고,

새로운 Controller를 추가하게 되어도 기존 구조를 바꾸지 않아도 된다.

 

스프링 MVC는 여태 우리가 발전시킨 회원관리 웹 어플리케이션의 과정과 유사하다.

이에 대한 학습은 다음 강의 들으면서 천천히 정리해보겠다~!


본 게시글은 김영한 님의 '스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 강의를 구매 후 정리하기 위한 포스팅입니다.

내용을 임의로 추가, 수정, 삭제한 부분이 많으며 정확한 이해를 위해서 강의를 구매하시는 것을 추천 드립니다.

 

inf.run/B756

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

www.inflearn.com

 

반응형

'Develop > Java' 카테고리의 다른 글

[Java] static과 synchronized  (0) 2021.05.26
[Java] Collections와 Map  (1) 2021.05.21
[MVC] Controller 단순화  (0) 2021.05.13
[MVC] Model 분리  (0) 2021.05.11
[MVC] View 분리  (0) 2021.05.10