지난 포스팅에서는 html을 동적으로 생성하는 책임을 서블릿에서 분리하였다.
@WebServlet(name = "mvcMemberSaveServlet",urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {
MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// [ 요청 데이터 가져오기 ]
String username = request.getParameter("username");
int age =Integer.parseInt(request.getParameter("age")) ;
// [ 비즈니스 로직 ]
Member member =new Member(username,age);
memberRepository.save(member);
// [ 응답 데이터 JSP로 보내기 ]
request.setAttribute("member",member); // 전달할 데이터를 request의 내부저장소에 보관
// [ View 영역 ]
String viewPath = "/WEB-INF/views/save-result.jsp"; // JSP 페이지 경로
MyView myView = new MyView(viewPath); //MyView 생성
myView.render(request,response); // 렌더링
}
}
View는 분리했지만 모든 서블릿이 View를 호출하는 공통로직을 가지게 되었다. 이는 많은 중복코드를 만들어낸다. 그럼 이를 어떻게 처리해야 할까?
프론트 컨트롤러 패턴 ( Front Controller Pattern )
View 생성에 중복코드가 발생하는 이유는 다음과 같다. 서버로 들어오는 Request는 url에 맞는 서블릿과 매핑된다. Request와 서블릿이 일대일로 매핑되니 서블릿마다 View를 생성하는 로직이 필요해진다. 이런 이유로 중복코드가 발생한다. 이런 문제를 해결하는 디자인 패턴이 '프론트 컨트롤 패턴'이다.
서버로 들어오는 Request는 모두 하나의 서블릿을 거친다. 그것이 프론트 서블릿이다. 프론트 서블릿은 요청이 들어온 url과 매핑되는 Controller에게 요청데이터를 넘긴다. Controller는 비즈니스 로직을 처리하고 그 결과를 프론트 서블릿에게 전달한다. 결과를 전달받은 프론트 서블릿은 View에게 데이터를 넘겨 화면을 동적으로 생성한다.
View에는 오로지 FrontServlet 하나만 접근하니 중복코드 생성을 막을 수 있다. 이렇듯, 프론트 컨트롤러 패턴은 서블릿에서 비즈니스로직을 처리하는 책임을 Controller로 분리하고 그 외 공통작업은 프론트 서블릿에서 처리하도록 하여, 중복코드 생성을 막고 성능을 최적화한다.
FrontServlet
@WebServlet(name = "frontControllerServlet",urlPatterns = "/front-controller/*")
public class FrontControllerServlet extends HttpServlet {
private Map<String, Controller> controllerMap = new HashMap<>();
// 컨트롤러 탐색 메소드
public FrontControllerServlet() {
controllerMap.put("/front-controller/members/new-form",new MemberFormController());
controllerMap.put("/front-controller/members/save",new MemberSaveController());
controllerMap.put("/front-controller/members",new MemberListController());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// [ STEP1. 요청과 매핑되는 Controller 찾기 ]
String requestURI = request.getRequestURI();
ControllerV2 controller = controllerMap.get(requestURI);
// [ 컨트롤러를 찾지 못한 경우, 404 에러페이지 전달
if(controller == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// [ STEP2. 컨트롤러에 요청데이터를 전달하고 비즈니스 로직 처리결과 받기 ]
MyView view = controller.process(request, response);
// [ STEP3. VIEW 영역에 결과 데이터 전달 ]
view.render(request,response);
}
}
프론트 서블릿은 3가지 STEP으로 진행된다.
1. 요청과 매핑되는 Controller 찾기
2. Controller에 요청 데이터 전달 및 결과 받기
3. 결과 데이터 VIEW 영역에 전달
서블릿에 설정된 urlPattern은 /front-controller/* 이다. 이 말은 즉, http://localhost:8080/front-controller/ 로 시작하는 요청은 모두 해당 서블릿으로 매핑된다는 의미이다. 서블릿으로 요청이 들어오면 url과 온전히 일치하는 Controller를 탐색한다. 찾지 못하면 404 에러를 발생시킨다. controller를 찾으면 요청데이터를 전달하고 비즈니스 로직을 실행한다. 그리고 그 결과를 View객체로 반환 받는다. View 객체를 반환받은 프론트 서블릿은 render 메소드를 호출하여 VIEW 영역에 결과 데이터를 전달한다.
Controller
public class MemberSaveController implements Controller {
MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// [ 비즈니스 로직 ]
String username = request.getParameter("username");
int age =Integer.parseInt(request.getParameter("age")) ;
Member member =new Member(username,age);
memberRepository.save(member);
// [ 결과 반환하기 ]
request.setAttribute("member",member); //request 내부저장소에 처리 결과 데이터 저장하기
return new MyView("/WEB-INF/views/save-result.jsp"); // VIEW 객체 반환
}
}
Controller 인터페이스로 Controller 구현체를 만들어 비즈니스 로직을 처리한다. 비즈니스 로직을 처리하면 그 결과를 Request 객체에 저장하고 View 객체를 생성하여 반환한다. View를 Controller에서 생성하는 이유는 Controller마다 가리키는 JSP 페이지 경로가 다르기 때문이다. Controller에게 View 객체를 전달받은 프론트 서블릿은 VIEW 영역에 결과 데이터를 전달한다. 그러면 VIEW 영역은 데이터를 가지고 html을 동적으로 생성하여 클라이언트에게 전달한다.
이렇게 프론트 컨트롤러 패턴으로 비즈니스 로직과 공통처리 로직을 분리하여 확장성을 올리고 중복코드를 제거하였다. 여기까지 MVC(Model-View-Controller) 패턴의 V와 C를 알아 보았다. 그러나 아직 한 가지 문제가 더 있다. Controller가 request와 response 객체에 의존하고 있다. Controller는 비즈니스로직을 처리하는 클래스로 비즈니스 로직과 관련된 데이터만 필요하다. request와 response 객체는 그 외 header, cookie, session 같은 비즈니스 로직과 관련없는 데이터를 가지고 있다. 그러므로 Controller와 request, response를 분리해야 한다. 이를 위해서, 우리는 MVC 패턴의 M, Model 알아야 한다. 이는 다음 포스팅에서 다루어 보겠다.
참고자료
'SPRING > Spring MVC' 카테고리의 다른 글
[SpringMVC] MVC 패턴 구현하기(4) - Adapter (0) | 2023.08.09 |
---|---|
[SpringMVC] MVC 패턴 구현하기(3) - Model (0) | 2023.08.09 |
[SpringMVC] MVC 패턴 구현하기(1) - View (0) | 2023.08.08 |
[SpringMVC] 응답(Response)의 종류 ( Text, Html, Json ) (0) | 2023.08.08 |
[SpringMVC] 요청(Request)의 종류 ( GET, POST, JSON ) (0) | 2023.08.02 |