1. MVC 만들기 미션 [2단계 : 리팩토링]을 완성해서 제출했다.
저번 톰캣 미션에서 만든 '컨트롤러 인터페이스 기반 MVC 프레임워크'와
MVC미션 1단계에서 만든 '어노테이션 기반 MVC 프레임워크'를
공존하게 만들기!!
핵심은
1. ManualHandlerMapping와 AnnotationHandlerMapping를 HandlerMapping로 추상화
2. 각각을 처리할 수 있는 ManualHandlerAdapter와 AnnotationHandlerAdapter를 만들고 HandlerAdapter로 추상화
https://github.com/woowacourse/jwp-dashboard-mvc/pull/491
2. 김영한님의 MVC1편의 3,4 강의자료를 슥슥 읽었다.
📍 Model-View-Controller
컨트롤러
- HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다.
- 그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다
모델
- 뷰에 출력할 데이터를 담아둔다.
- 뷰가 필요한 데이터를 모두 모델에 담아서 전달해주는 덕분에 뷰는 비즈니스 로직이나 데이터 접근을 몰라도 되고, 화면을 렌더링 하는 일에 집중할 수 있다.
뷰
- 모델에 담겨있는 데이터를 사용해서 화면을 그리는 일에 집중한다. 여기서는 HTML을 생성하는 부분을 말한다.
📍 Front Controller 도입
FrontController 패턴 특징
- 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
- 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
- 입구를 하나로!
- 공통 처리 가능
- 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨
스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 되어있다!
📍 View 도입
뷰를 처리하는 객체
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);
}
private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
// JSP는 request.getAttribute() 로 데이터를 조회하기 때문에, 모델의 데이터를 꺼내서 request.setAttribute() 로 담아둔다.
model.forEach((key, value) -> request.setAttribute(key, value));
}
}
dispatcher.forward() : 다른 서블릿이나 JSP로 이동할 수 있는 기능이다. 서버 내부에서 다시 호출이 발생한다.
📍 Model 도입
- 서블릿 종속성 제거
컨트롤러 입장에서 HttpServletRequest, HttpServletResponse이 꼭 필요할까?
요청 파라미터 정보는 자바의 Map으로 대신 넘기도록 하면 지금 구조에서는 컨트롤러가 서블릿 기술을 몰라도 동작할 수 있다.
그리고 request 객체를 Model로 사용(request.setAttribute())하는 대신에 별도의 Model 객체를 만들어서 반환하면 된다. 우리가 구현하는 컨트롤러가 서블릿 기술을 전혀 사용하지 않도록 변경해보자.
이렇게 하면 구현 코드도 매우 단순해지고, 테스트 코드 작성이 쉽다.
- 뷰 이름 중복 제거
컨트롤러에서 지정하는 뷰 이름에 중복이 있는 것을 확인할 수 있다.
컨트롤러는 뷰의 논리 이름을 반환하고, 실제 물리 위치의 이름은 프론트 컨트롤러에서 처리하도록 단순화 하자.
이렇게 해두면 향후 뷰의 폴더 위치가 함께 이동해도 프론트 컨트롤러만 고치면 된다.
public class ModelView {
private String viewName;
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName) {
this.viewName = viewName;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public Map<String, Object> getModel() {
return model;
}
public void setModel(Map<String, Object> model) {
this.model = model;
}
}
📍 Adapter 패턴 적용
어댑터 패턴을 사용해서 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 변경
public interface MyHandlerAdapter {
boolean supports(Object handler); // 어댑터가 해당 컨트롤러를 처리할 수 있는지 판단
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
3. [펀잇] ReviewCount, FavoriteCount의 동시성 처리에 대해 팀원들과 논의했다.
기타
Objects.isNull vs Object == null
Objects.isNull 메서드는 Stream의 filter 메서드처럼 인자를 Predicate 인터페이스로 받는 메서드에 사용할 때 사용.
감정회고
1. 저번 미션에 대한 리뷰어 피드백을 전달받았다!
ㅋㅋㅋㅋㅋㅋㅋ 레벨1,2때는 모든 미션이 끝난 후 리뷰어 또는 페어의 피드백을 다 섞어서 전달해주셔서
누가 어떤 피드백을 남겼는지 알 수 없었는데
이번에는ㅋㅋㅋㅋㅋㅋㅋ 미션 끝나자마자 리뷰를 전해주셔서 ㅎ 리뷰어의 익명성이 사라졌다 ><
요즘 좀 심신미약이라 슬펐는데.. 피드백 보고 감동받아서 힘이났다..
고마워요 하디... 진짜 감동먹음...😭
저도 하디랑 함께한 톰캣 미션 너무 좋았어요ㅠㅠ
2. 구글링말고 공식문서!
우가와 대화하다 배운점!
오래전에 올라온 스택오버플로우글은 믿을 수 없다! 그 사이에 업데이트 될 수도 있으니!!
그러므로 공식문서를 확인하자~~