[spring] 프론트 컨트롤러 패턴

정원석·2024년 5월 15일

프론트 컨트롤러 소개


FrontController의 특징

  • 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
  • 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 된다.

프론트 컨트롤러 V1 사용 구조


클라이언트가 FrontController에게 HTTP를 요청하면 URL 매핑 정보에서 컨트롤러 조회 → 컨트롤러 호출 → 컨트롤러에서 JSP에게 요청을 보냄 → JSP에서 클라이언트에게 HTML을 응답한다.

ControllerV1

package hello.servlet.web.frontcontroller.v1;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public interface ControllerV1 {

    void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
  • HttpServletRequest : HTTP요청 정보를 담고 있는 객체. 이 객체를 통해 요청 파라미터, 헤더, 세션 정보 등을 접근한다.
  • HttpServletResponse : HTTP 응답 정보를 담고 있는 객체. 이 객체를 통해 클라이언트에게 전송할 응답의 상세 코드, 헤더, 콘텐츠 등을 설정할 수 있다.

서블릿과 비슷한 모양의 컨트롤러 인터페이스를 구현한다. 각 컨트롤러는 이러한 형식으로 인터페이스를 구현한다. 프론트 컨트롤러는 이 인터페이스를 호출해서 구현과 관계없이 로직의 일관성을 가져간다.


HTTP응답에서 헤더가 필요한 이유

FrontController과는 다른 이야기지만 잠시 정리하고 넘어가야겠다.

1. 응답의 메타데이터 제공

HTTP헤더는 응답의 메타데이틀 제공한다. 예를들어, 응답의 콘텐츠 유형(Content-Type), 길이(Content-Length), 인코딩(Content-Encoding) 등을 포함한다. 클라이언트는 이러한 메타데이터를 사용하여 응답을 적절히 처리할 수 있다.

Content-type : text/html;
charset=UTF-8;
Content-Length: 342;

2. 캐싱 제어

헤더를 사용하여 캐싱 정책을 설정할 수 있다. 이를 통해 서버는 클라이언트와 중간 프록시 서버에게 응답을 얼마나 오래 캐시할 수 있는지, 또는 캐시를 무효화할 시점을 지정할 수 있다.

3. 세션 관리 및 상태 유지

쿠키 설정과 같은 헤더를 사용하여 세션 관리 및 상태 유지를 할 수 있다. 서버는 클라이언트에게 세션ID를 전달하고, 클라이언트는 이 후 요청시 세션 ID를 사용한다.


다시 FrontController 설명으로 돌아가겠다.

MemberFormController - 회원 등록 컨트롤러

package hello.servlet.web.frontcontroller.v1.controller;

import hello.servlet.web.frontcontroller.v1.ControllerV1;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class MemberFormControllerV1 implements ControllerV1 {

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String viewPath = "/WEB-INF/views/new-form.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}
  • String viewPath = "/WEB-INF/views/new-form.jsp"; : 뷰(JSP)파일의 경로를 나타낸다.
  • RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath); : 요청을 지정된 뷰(JSP)로 전달하는 역할을 함
  • dispatcher.forward(request, response); : 클라이언트의 요청을 "new-form.jsp"로 포워딩 한다. 이로인해 클라이언트는 새로운 멤버 등록 폼을 볼 수 있다.

MemberSaveController - 회원 저장 컨트롤러

package hello.servlet.web.frontcontroller.v1.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v1.ControllerV1;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class MemberSaveController implements ControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public void 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);

        //Model에 데이터를 보관한다.
        request.setAttribute("member", member);

        String viewPath = "/WEB-INF/views/save-result.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}
  • ControllerV1 인터페이스를 구현하고, 클라이언트의 요청을 받아 회원 정보를 저장 후 결과 페이지로 포워딩하는 역할을 한다.
  • private MemberRepository memberRepository = MemberRepository.getInstance(); :회원 정보를 저장하고 관리하는 레포지스토리 인스턴스이다. MemberRepository 클래스의 getInstance 메소드를 사용해 싱글톤 인스턴스를 가져온다.
  • username과 age 파라미터를 추출한다.
  • Member 객체를 생성하고 memberRepository 를 통해 저장한다.
  • 결과를 보여줄 JSP경로를 설정하고, RequestDispatcher를 사용해 해당 JSP로 포워딩 한다. 이를 통해 클라이언트는 "save-result.jsp"페이지를 보게된다.

MemberListControllerV1 - 회원 목록 컨트롤러

package hello.servlet.web.frontcontroller.v1.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v1.ControllerV1;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.List;

public class MemberListControllerV1 implements ControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();


    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Member> members = memberRepository.findALl();
        request.setAttribute("members", members);
        String viewPath = "/WEB-INF/views/members.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}
  • List members = memberRepository.findAll();

FrontControllerServletV1 - 프론트 컨트롤러

package hello.servlet.web.frontcontroller.v1;

import hello.servlet.web.frontcontroller.v1.controller.MemberFormControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberListControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberSaveController;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {

    private Map<String, ControllerV1> controllerMap = new HashMap<>();

    public FrontControllerServletV1() {
        controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());
        controllerMap.put("/front-controller/v1/members/save", new MemberSaveController());
        controllerMap.put("/front-controller/v1/members", new MemberListControllerV1());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FrontControllerServletV1.service");

        String requestURI = request.getRequestURI();

        ControllerV1 controller = controllerMap.get(requestURI);
        if (controller == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
         }
        controller.process(request,response);
        }
}
  • @WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/") : frontControllerServletV1 서블릿을 /front-controller/v1/ 경로에 매핑한다. 즉 이 경로로 들어오는 요청을 이 서블릿이 처리한다.
  • controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1()); : 생성자에서 controllerMap에 url과 컨트롤러를 매핑한다. 예를들어, /front-controller/v1/members/new-form 요청이 들어오면 MemberFormControllerV1 이 실행된다.
profile
Back-End-Dev

0개의 댓글