Spring MVC - frontController
해당 포스팅은 김영한님의 스프링 MVC 1편 강의를 내용을 정리한 글입니다.
모든 코드와 그림의 저작권은 김영한 님에게 있습니다.
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의 - 인프런
웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원
www.inflearn.com
이전 포스팅에서는 서블릿, JSP 를 통해서 어떻게 웹 서비스 개발을 했는지, 그리고 MVC 패턴의 프론트 컨트롤러가 없을때 어떤 문제가 발생하는지 알아보았다.
이번 포스팅에서는 프론트 컨트롤러의 도입과 개발자 편의성을 높이기 위해서 기능 확장을 하는 단계별로 한번 알아보자
Spring MVC 패턴 - Servlet
Spring MVC - Servlet Spring MVC를 통한 Model View Controller 구조가 일반적으로 사용하는 웹 어플리케이션의 구조이다. 그럼 MVC 패턴을 통한 구조를 만들어서 개발하는 것이 어떤 장점이 있는지 한번 알아
developer-dodo.tistory.com
1. FrontController V1
프론트 컨트롤러란 무엇인가?
프론트 컨트롤러 즉 모든 요청을 서블릿 하나로 클라이언트의 요청을 받는다.
프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
공통 처리 로직을 프론트 컨트롤러에서만 진행하면 되며, 나머지 컨트롤러를 서블릿을 사용하지 않아도 되는 장점이 있다.
구조적으로 기존과 어떻게 달라지는지 알아보자
이 기존의 MVC 구조를 따르는데 프론트 컨트롤러만 추가된 로직이다.
서블릿과 흡사한 컨트롤러 인터페이스를 만들고 각 컨트롤러들은 이 인터페이스를 구현한 구현체이다.
프론트 컨트롤러는 인터페이스를 호출하여 구현과 관계없이 일정한 로직을 수행한다.
영한님도 말씀하신 OCP 원칙을 지키기 위해서 인터페이스를 사용하고 기능과 구현을 분리하는 것이 중요하다고 늘 강조하신다.
2. FrontController V2
V1 버전에서 발생되는 가장 반복적인 코드는 컨트롤러가 뷰를 호출하는 과정이다.
String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
모든 컨트롤러가 뷰 이름을 각각 불러 호출해야하고 이 과정을 처리하는 객체를 만들어 해결해보자
MyView 라는 뷰 처리 객체를 만들어서 해결이 가능하다.
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);
}
}
코드만 확인하면 이전에 반복되는 작업을 render 라는 함수로 수행하는 소스이다.
간단하게 설명하자면 view이름을 String 타입으로 일일히 지정하고 모든 컨트롤러에서 이 로직을 개발해야 했다.
하지만 이 로직을 즉 뷰를 호출하는 render라는 로직을 프론트 컨트롤러에서 수행하고 컨트롤러는 view이름이 담긴 Myview라는 객체만 반환해 주는 것이다.
이를 통해서 컨트롤러는 반복 작업을 줄이고 모델에 데이터를 담아서 뷰 렌더링 객체와 함께 반환만 해주면 되는 것이다.
3. FrontController V3
v3에서 사용하는 해결하는 문제는 서블릿 사용이다.
컨트롤러는 사실 서블릿 request, response 객체가 필수적이지 않다.
특히 response 같은 경우에는 더더욱 그렇다. 데이터를 모델에 담아서 뷰에 렌더링 하는 과정에 전달하기 때문이다.
컨트롤러가 서블릿 기술을 몰라도 동작하게끔 수정해보려 한다.
요청받은 파라미터는 Map 인터페이스 구현체를 활용해서 전달한다.
뷰 이름의 중복을 제거한다.
컨트롤러는 뷰의 논리이름을 반환하고, 물리이름은 프론트 컨트롤러가 처리하는 방식으로 먼저 바꾼다.
여기서 논리이름이란 아래 예시를 참고하자
- /WEB-INF/views/new-form.jsp -> new-form
- /WEB-INF/views/save-result.jsp -> save-result
- /WEB-INF/views/members.jsp -> members
이렇게 구조를 변경시 뷰의 폴더 위치가 변경되도 프론트 컨트롤러 코드만 수정하면 바로 사용 가능
v2와 달라진 점은 viewResolver라는 기능이 새로 도입되었다.
그리고 컨트롤러가 MyView를 반환하는게 아니라 ModelView 라는 객체를 반환한다.
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;
}
}
이 모델 뷰는 데이터 전달할 모델(Map 인터페이스), 뷰 이름을 가지고 있다.
여기서 뷰 이름은 논리 이름이다.
컨트롤러는 기존과 로직은 동일하지만, ModelView객체 호출할 뷰 이름, 그리고 모델에 데이터를 추가해야한다.
◼ ViewResolver
viewResolver는 뷰의 논리이름을 물리 경로로 변경하는 작업을 수행한다.
앞서 언급한 것처럼 members 라는 논리 이름이 들어오면 이를 /WEB-INF/views/members.jsp 로 바꿔주는 것이다.
그리고 view.render() 함수를 통해서 모델, 리퀘스트, 리스폰스를 전달한다.
여기서 render의 함수에 달라진 점이 하나 있다.
model을 우리는 Map 인터페이스로 구현하였는데 JSP는 request의 Attribute에서 설정된 값을 가져온다.
그렇게 때문에 render 함수에서 model의 모든 key,value를 setAttribute로 request 객체에 넣어줘야한다.
4. FrontController V4
v4는 개발자 편의성을 높인 버전이다.
개발자 입장에서는 항상 ModelView를 생성하고 반환하는 부분이 다소 번거롭다.
실용성이 다소 떨어지는 부분이 생기는 것이다.
그래서 수정한 것은 ModelView를 생성하는게 아닌 ViewName만 반환하는 방식으로 바꾸는 것이다.
모델을 프론트 컨트롤러에서 생성해서 인자로 전달하기 때문에 컨트롤러는 모델을 생성하지 않고 put을 통해서 모델 데이터만 넣어주면 된다.
public class MemberListControllerV4 implements ControllerV4 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public String process(Map<String, String> paramMap, Map<String, Object> model) {
List<Member> members = memberRepository.findAll();
model.put("members", members);
return "members";
}
}
생성된 Map 객체를 인자로 넘겨주기 때문에 같은 인스턴스에 추가하는 것이다.
이렇게 되면 컨트롤러는 모델에 데이터를 담기만 하고 viewName만 반환하면 되는 것이다.
개발자 입장에서는 프레임워크가 조금 복잡한 일을 수행하지만 실제로 개발할때는 훨씬 코드가 간편해 지는 것이다.
5. FrontController V5
이제 v5는 여러 컨트롤러 구현체를 모두 사용해야 하는 경우, 즉 한가지 방식의 컨트롤러에 국한되지 않은 아주 유연한 버전이다.
이를 구현하기 위해서 어뎁터 패턴을 추가해야한다.
◼ 어뎁터 패턴
어뎁터 패턴에는 두가지 요소가 추가된다.
핸들러 어뎁터 목록과 핸들러 어뎁더가 있다.
그럼 핸들러는 뭐고 어뎁터는 무엇을 의미하는지 알아보자
핸들러 어뎁터 이름을 해석해보면 핸들러를 적용한느 역할 정도로 보여진다.
중간에 어뎁터 역할을 하는 것이는 이 역할을 통해서 다양한 종류의 컨트롤러를 호출하는 것이다.
쉽게 생각하자면 어뎁터는 내가 110v와 220v의 충전기 변환 어뎁터 역할이다.
이 어뎁터를 통해서 프론트 컨트롤러는 하나의 컨트롤러만 쓰는게 아니라 어뎁터로 원하는 핸들러 즉 컨트롤러를 쓸수다.
핸들러는 컨트롤러의 넓은 범위의 이름으로 이전과 다르게 핸들러 어뎁터를 통해서 해당 종류에 맞는 여러 컨트롤러가 존재하기 때문이다.
위 그림의 로직을 분석해보자
먼저 요청이 들오와서 프론트 컨트롤러를 거치게된다.
요청에 맞는 맞는 핸들러를 조회한다. 그리고 그 핸들러를 처리가능한 어뎁터가 존재하는지 확인한다.
처리 가능한 어뎁터가 있으면 그 핸들러 어뎁터로 리퀘스트 처리를 요청한다. 어뎁터가 실제 컨트롤러를 에서 로직을 처리하고 모델에 데이터를 담는다.
핸들러 어뎁터는 ModelView를 반환한다. 이때 ModelView란 모델에 데이터와 view리졸버를 통해서 렌더링할 뷰 이름을 가지고 있는 객체이다.
프론트 핸들러는 뷰리졸버에 뷰이름을 통해서 MyView를 반환받어 화면에 데이터를 랜더링 해준다.
이렇게 하는 이유는 여러개의 컨트롤러 즉 핸들러를 사용하기 위함이다.
요청에 맞는 핸들러를 찾고 그 핸들러를 처리 가능한 어뎁터를 조회하고 어뎁터를 통해서 요청을 처리하고 모델뷰를 반환받는 것이다. 결론적으로 다형성을 유지한채로 아주 쉽게 컨트롤러나 다른 개발에서 확장이 가능한 장점이 있다.
마지막으로 개발한 V5 버전이 실제 현업에서 가장 많이 사용하는 Spring MVC의 동작 원리와 매우 비슷하다.
다음에는 Spring MVC의 동작 원리가 실제로는 어떻게 동작하는지 한번 상세하게 알아보자
'Spring' 카테고리의 다른 글
HandlerMethodArgumentResolver 동작 (1) | 2024.06.10 |
---|---|
[Spring Petclinic]1. application properties (0) | 2024.04.27 |
Spring MVC 패턴 - Servlet (0) | 2024.03.06 |
Spring 빈 생명주기 (0) | 2024.02.15 |
Spring 의존관계 자동 주입 (4) | 2024.02.09 |