MVC 프레임 워크 만들기
FrontController 패턴 특징
-프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
-프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출 입구를 하나로!
-공통 처리 가능
-프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨
-스프링 웹 MVC의 핵심도 바로 FrontController
-스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있음
프론트 컨트롤러 도입
1. ControllerV 라는 인터페이스를 만들고 이 인터페이스에서 process라는 메소드를 하나 만든다. 이 메소드는 HttpServlet에 service 메소드와 똑같다.
2. ControllerV를 상속받는 Controller class 를 만든다 (회원가입, 저장 , 조회)
>> 각 로직은 servlet과 mvc를 활용한 코드를 그대로 사용했다.
3. 프론트 컨트롤러 구현 (FrontControllerServlet)
3-1) HttpServlet를 상속받고 Map을 활용했다.>> Map<String, ControllerV> controllerMap << 으로
key: 매핑 URL이고 value: 호출될 컨트롤러이다. 즉, String은 경로 , ControllerV는 호출될 컨트롤러 이다.
3-2) 프론트 컨트롤러 생성자에 일단 위에서 만든 Controller class 와 각 경로들을 정해서 controllerMap에 .put 시켜준다.
3-3) 프론트 컨틀로러 service 메소드에 String requestURI = request.getRequestURI() 코드를 넣어서 이 service를 호출한 경로를 확인 할 수 있다. 먼저 requestURI 를 조회해서 실제 호출할 컨트롤러를 controllerMap 에서 찾는다.
3-4) 이 경로를 이용해서 생성자에서 만든 Map과 일치한 경로가 있는지 확인을 한다 - > ControllerV1 controller = controllerMap.get(requestURI)
3-5) 없으면 .setStatus(404) 를 반환하고, 있으면 controller.process(request, response); 을 호출해서 해당 컨트롤러를 실행한다.
*다형성을 활용하여 프론트 컨트롤러를 도입했지만 Controller class 안에 공통된 부분들이 아직도 존재한다. 이런 부분을 더 해결할 필요가 있다*
View 분리
String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
각 Controller class 에 viewPath 라는 변수에 경로를 받고 request.getRequestDispatcher(viewPath) 해서 .forward() 시키는 작업이 중복이 됐다. 이를 한 곳에서 처리하기 위해 View를 분리했다.
1. MyView라는 클래스를 만들고 여기 안에서 viewPath 를 필드에 선언하고 생성자에 이 viewPath를 초기화 시켜준다.
그리고 render라는 함수를 만들어서 위에 request.getRequestDispatcher(viewPath) 해서 .forward() 시키는 작업을 render함수 안에 넣어준다.
public void render(HttpServletRequest request, HttpServletResponse response) {
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
2. 프론트 컨트롤러 도입과 똑같은 방식으로 하는데, 각 Controller class process 메소드에 return new Myview("경로") 를 반환한다.
*위와 같은 방식으로 하면 각 Controller class 에 중복되는 코드들이 없어지는데 이 구조는 프론트 컨트롤러에서 컨트롤러를 호출하면 그 컨트롤러에서 MyView를 반환하고 MyView 클래스에서는 render함수가 있는데 이 함수는 프론트 컨트롤러에 service 함수에서 실행이 된다. 그러면 그 경로에 맞게 JSP 파일이 실행되게 된다*
Model 추가(가장 중요)
컨트롤러에서 HttpServletRequest , HttpServletResponse가 꼭 필요하지는 않다. 요청 파라미터 정보는 Map 담아 놓고 필요할때 꺼내서 쓰는 형식으로 바꾸면 된다.
서블릿의 종속성을 제거하기 위해 Model을 직접 만들어 코드를 리팩토링 한다. 추가로 뷰 이름이 중복 되는 것을 제거 할 것이다.
이것을 하는 이유는 각 컨트롤러에 경로를 입력해주는데 "/WEB-INF..." 여기서 경로가 바뀌게 된다면 모든 컨트롤러를 다 바꿔줘야 하지만 이렇게 한 곳에서 경로를 반환해주면 한 곳에서만 코드를 바꾸면 되기 때문에 더 좋은 설계가 되기 때문이다.
1. 서블릿의 종속성을 제거하기 위해 Mdoel을 직접 만들어야 한다. 그래서 ModelView class 를 만들어주고 viewName (new-form,save-result..) , Map model 을 필드로 가진다.
2. 각 컨트롤러 클래스는 ModelView 로 반환이 되고 파라미터로 Map<String, String> paramMap 이 넘어온다.
(paramMap에는 쿼리 파라미터 정보가 들어있다. 서블릿의 종속성을 제거 하려고 하기 때문에 request.getAttribute() 로 가져오는것이 아닌 우리가 paramMap에 정보를 담는다.)
3. 회원 등록 폼에서는 paramMap에서 정보를 사용할게 없기 때문에, return new ModelView("new-form") 으로 논리적인 이름만 반환한다. 하지만 저장할때는 쿼리 파라미터 정보가 필요하기 때문에 paramMap.get("useranme") 처럼 정보를 가져와서 member 객체를 만들고 repository에 저장을 하고 ModelView mv = new ModelView("save-result"); 하면서 논리적인 이름만 반환을 하는데, 여기서 member 객체도 같이 전달해야한다. mv.getModel().put("member",member);
- paramMap.get("username");
>> 파라미터 정보는 map에 담겨있다. map에서 필요한 요청 파라미터를 조회하면 된다.
- mv.getModel().put("member", member);
>> 모델은 단순한 map이므로 모델에 뷰에서 필요한 member 객체를 담고 반환한다.
4. 프론트 컨트롤러 수정
4-1) 위에서 서블릿 종속성을 제거하기 위해 쿼리 파라미터 정보를 paramMap에 담는다고 했기 때문에 이 코드를 작성한다. 모든 쿼리 파라미터를 조회하는 것은 전에 배웠기 떄문에 자세한 코드는 생략.
4-2) controller.process(paramMap) 을 실행하고 반환되는 ModelView mv 를 활용해서 경로를 설정해준다. mv.getViewName() 으로 논리적인 이름만 뽑아 놓고 문자열 조합을 이용해서 (viewResolver) MyView view 객체를 새로 만들고 view.render()로 랜더링을 해준다.
4-3) .render() 를 할 때 원래 파라미터로 request, response만 넘겨줬는데 이것은 우리가 request.setAttribute()를 이용해서 정보를 담았기 때문에 가능했다. 하지만 우리는 model에 담았기 때문에 .render(mv.getModel() , request, response) 이렇게 3개의 파라미터를 날려줘야한다.
4-4) MyView 클래스에 3개의 파라미터가 넘어왔을때 render 메소드를 추가해준다. 이때 model에 있는 정보를 다시 request 객체에 저장을 해야하기 때문에 model.forEach((key, value)->request.setAttribute(key, value)); 코드를 추가한다.
4-5) 그런후 똑같이 request.getRequestDispatcher(viewPath) 를 해줘서 .forward(request, reponse)를 실행하면 된다.
단순하고 실용적인 컨트롤러
서블릿 종속성을 제거하고 뷰 경로의 중복을 제거하는 등, 잘 설계된 컨트롤러이다. 그런데 실제 컨트롤러 인터페이스를 구현하는 개발자 입장에서는 조금 번거롭다.
구조는 위와 같지만, 컨트롤러에서 ModelView를 반환하지 않고 ViewName만 반환한다.
1. ControllerV의 인터페이스에 process 메소드는 ModelView로 반환되는게 아니라 String 으로 반환이 되는데,
이는 뷰의 이름을 반환한다는 의미이다. 그러면 ModelView에서 사용하는 model 객체는 파라미터로 받는 것이다.
ex) String process(Map<String,String> paramMap , Map<String,Obejct> model);
>>ModelView에서 model을 관리하면 번거로움이 있었는데 model을 프론트 컨트롤러에서 관리하면 아주 간결하고 실용적이게 코드를 작성할 수 있다.
2. 회원 등록 폼에서는 그냥 return "new-form" 이런식으로 viewName만 리턴을 시키고 회원 저장 폼에서는 정보를 가져와야 하는데 파라미터로 model이 넘어오기 때문에 model.put("member",member); return "save-result"; 의 코드로 아주 간결해진다. 회원 목록 조회도 model.put("members",members); return "members";
3. model을 프론트 컨트롤러에서 생성한다고 했는데 Map<String,Object> model = new HashMap<>() 으로 model을 생성하고, 각 컨트롤러에서 메소드 process를 실행시킬때 원래는 paramMap만 넘겨줬지만 model까지 같이 넘겨준다.
4. 그럼 return 되는 값은 viewName("new-form"...) 이걸로 viewResolver 메소드를 이용해 완벽한 경로 이름을 만들고 이를 view.render()를 해준다.
5. 위에서는 .render(mv.getModel()..) 로 모델 객체를 꺼내서 넘겨줬지만 프론트 컨트롤러에서 model을 만들고 관리하기 때문에 view.render(model...) 으로 넘기면 된다.
'Spring' 카테고리의 다른 글
상속 추상화, 캡슐화, 다형성 개념 (0) | 2024.03.29 |
---|---|
서블릿(Servlet)이란? (JSP(Java Server Page)) (0) | 2023.03.02 |
Spring MVC 3 - MVC 활용 (1) | 2023.02.27 |
Spring MVC - 2 (서블릿) (0) | 2023.02.27 |
Spring MVC 1 - 웹 서버와 웹 애플리케이션 서버(WAS) (0) | 2023.02.27 |