이번 강의는 김영한 강사님의 강의를 토대로 정리한 글입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
간단히 구현한 MVC 프레임워크
이제부터 SpringMVC 패턴이 아닌 이를 직접 구현해보고 나서 몇가지들을 정리해보겠다.
Front Controller 패턴 간단 소개
나는 이전 포스팅의 마지막에서 공통 처리를 하기 위해서, 하나의 경로로 모아주는 Servlet이 필요하다고 언급한 바 있다.
하지만 Front Controller 패턴을 도입하면 다음 사진과 같이 공통 부분을 하나의 Servlet에서 처리하고, 세부 역할은 컨트롤러가 맡도록 하면 된다. (김영한 강사님의 강의를 MVC2 까지 마무리 짓고 오는 상황에서 정리하고 있습니다. 느낀 점은 Spring MVC 패턴의 핵심은 Front Controller라고 느끼고 있습니다. 이 개념은 확실하게 이해하셔야 한다고 생각합니다. SpringMVC 에서 Front Controller는 DispatcherSerlvet 으로 구현되어 있습니다.)
Front Controller 패턴의 특징은?
- Front Controller Servlet 하나로 모든 클라이언트 request를 받음
- 이후, 요청에 맞는 Controller 호출
- 이전 포스팅에서 말했던 단점들이, 입구는 하나가 되면서 공통 처리가 가능해졌고, 이 Front Controller 이외의 Servlet을 생성하지 않아도 됨
Front Controller
Front Controller라고 해서 별 다른것이 아니다. 이름 그대로 가장 앞에서 요청들을 컨트롤 하는 Servlet 객체이다.
따라서 이를 구현하기 위해서 Servlet 객체를 만들어야 하고, 대신 이 Front Controller는 모든 요청에 대해 매핑한다.
@WebServlet(name = "frontControllerServlet", urlPatterns = "/front-controller/*")
public class FrontControllerServlet extends HttpServlet {
그리고 아래의 사진과 같이, 이 Front Controller는 아래의 사진과 같은 로직을 구현해야 한다.
프론트 컨트롤러 간단 구현
아래와 같이 service()를 구현해봤다. service()는 클라이언트로부터 요청이 WAS로 전달되면 Thread가 알아서 service()를 호출해준다. (세세하게 정리하고 싶지만, 아무래도 세부 코드를 공유하기엔 무리가 있다고 판단했습니다. 자세한 강의는 위에서 언급한 강의를 수강하는걸 추천합니다.)
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Object handler = getHandler(req);
MyHandlerAdapter handlerAdapter = getHandlerAdapter(handler);
ModelView mv = handlerAdapter.handle(req, resp, handler);
MyView myView = viewResolver(mv.getViewName());
myView.render(req, resp, mv.getModel());
}
- getHandler()를 통해, 요청을 처리 가능한 핸들러를 찾아낸다.
- 반환된 Handler를 실행할 수 있는 HandlerAdapter를 찾아낸다.
- HandlerAdapter를 찾아내고, handle()을 호출하여 실제 컨트롤러의 로직을 실행하고 View Name(View의 논리 경로), Model이 담겨 있는 MyView를 반환한다.
- viewResolver()를 통해 MyView의 View Name을 사용해서 실제 resource의 경로가 있는 MyView를 생성하여 반환한다.
- MyView를 render()하여, 다른 resource로 이동한다. (render() 내부에 forward()가 있다.)
위의 과정들을 조금 더 자세하게 정리해보겠다.
Model, ModelView
이전까지, Controller에서 HttpServletRequest 객체의 setAttribute() 메서드를 사용하여 데이터를 전달하였지만, 이는 Servlet API에 종속적이며 테스트하기 어려운 단점이 있었다. 이를 해결하기 위해 Model 객체가 도입됐다. Model은 뷰에 전달할 데이터를 담는 객체로써, HttpServletRequest 객체의 setAttribute() 메서드와 유사한 역할을 한다. 하지만 Model은 Servlet API에 의존하지 않으므로, 테스트하기 쉽고 컨트롤러와 뷰 간의 결합도를 낮출 수 있다. 따라서 Model을 사용하면 코드의 가독성과 유지보수성이 향상된다.
ModelView는 Model 뿐만 아니라 ViewName까지 Wrapping 하고 있는 객체라고 생각하면 된다.
예시 코드 : Model
하지만 지금은, Spring MVC를 사용하지 않고 MVC 패턴을 구현하고 있기 때문에 model은 호출하는 쪽(Front Controller)에서 Map을 전달하고 이 model에 결과값을 담으면 Front Controller에서 Model을 알맞게 사용할 수 있다.
// 컨트롤러의 비즈니스 로직, String을 반환하는 것은 View의 논리 경로.
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
List<Member> members = memberRepository.findAll();
model.put("members", members);
return "members";
}
// jsp 파일에서 이와 같이 참조 가능
<c:forEach var="member" items="${members}">
<tr>
<td>${member.id}</td>
<td>${member.username}</td>
<td>${member.age}</td>
</tr>
</c:forEach>
예시 코드 : ModelView
ModelView는 마찬가지로 이렇게 내부에 model의 역할을 하는 HashMap과 viewName을 저장할 String 필드를 만들어주면 된다.
@Getter
@Setter
public class ModelView {
private String viewName;
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName) {
this.viewName = viewName;
}
}
ModelView를 사용하여 반환할 땐 이렇게 ModelView 내부의 model 객체에 결과 값을 저장해서 반환하면 된다.
@Override
public ModelView process(Map<String, String> paramMap) {
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member);
return mv;
}
그럼 이제 핸들러와 핸들러 어댑터에 대해서 간단히 알아보자.
핸들러, 핸들러 어댑터
- 핸들러 : 컨트롤러의 보다 넓은 개념이다. 컨트롤러가 호출되기 전에 클라이언트의 요청을 처리할 수 있는 핸들러를 매핑한다. SpringMVC 패턴에선 Handler의 한 종류인 HandlerMethod라고 있는데, 이 객체엔 컨트롤러의 여러 메타정보가 담겨 있다.
- 핸들러 어댑터 : 반환된 핸들러를 통해 비즈니스 로직을 실행하기 위해 일종의 어댑터가 필요하다. 핸들러 어댑터 덕분에 다양한 컨트롤러를 호출할 수 있다.
아래의 코드를 보면 조금은 더 이해가 될 것이다.
public class Controller1HandlerAdapter implements MyHandlerAdapter {
@Override
public boolean support(Object handler) {
return handler instanceof Controller1;
}
@Override
public ModelView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws ServletException, IOException {
Controller1 controller = (Controller1) handler;
return controller.process(createParamMap(req));
}
}
- support () : 핸들러 매핑을 통해 반환된 핸들러가 Controller1을 처리할 수 있는지 판단한다.
- handle () : support()에서 true가 나왔다면, 실제 컨트롤러로 typeCasting을 한 후에, 실제 컨트롤러의 비즈니스 로직을 수행한다. 수행한 결과로 ModelView를 만들어 FrontController에게 전달한다. (FrontController는 이 ModelView를 가지고 ViewResolver를 사용해서 View객체를 얻고 render() 할 수 있다.)
이렇게 한번 훑고 이걸 다시 보면 조금 더 이해가 되지 않았을까 싶다.
이번 강의는 코드가 90%라서 포스팅 하는데 어려움이 있었다ㅠㅠ
'스프링 > 스프링 MVC' 카테고리의 다른 글
스프링 MVC 기본 기능 (0) | 2023.05.16 |
---|---|
스프링 MVC 구조 (0) | 2023.05.15 |
Servlet의 한계, 템플릿 엔진, MVC 패턴 (0) | 2023.05.13 |
서블릿 (0) | 2023.05.12 |
Spring MVC : 컨테이너 (2) | 2023.05.11 |