4 분 소요

인프런 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술편을 학습하고 정리한 내용 입니다.

프론트 컨트롤러 패턴 소개

FrontController 패턴 특징

  • 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
  • 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
  • 입구를 하나로!
  • 공통 처리 가능
  • 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨.

스프링 웹 MVC와 프론트 컨트롤러 스프링 웹 MVC의 핵심도 바로 FrontController 스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있음.

프론트 컨트롤러 도입 - V1

프론트 컨트롤러를 단계적으로 도입해 보자. 이번 목표는 기존 코드를 최대한 유지하면서, 프론트 컨트롤러를 도입하는 것이다. 먼저 구조를 맞추어두고 점진적으로 리펙터링 해보자.

컨트롤러 인터페이스를 하나 만들자.

다음 위치에 만들었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
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;  
  
}

서블릿과 비슷한 모양의 컨트롤러 인터페이스를 도입한다. 각 컨트롤러들은 이 인터페이스를 구현하면 된다.

프론트 컨 트롤러는 이 인터페이스를 호출해서 구현과 관계없이 로직의 일관성을 가져갈 수 있다.

이제 이 인터페이스를 구현한 컨트롤러를 만들어보자. 지금 단계에서는 기존 로직을 최대한 유지 하는게 핵심이다

자 이제 멤버 저장 폼, 멤버 저장, 멤버 리스트 컨트롤러를 만들 거다.

다음과 같이 만들 것이고, 로직 자체는 MVC 패턴 적용 지난번에 했던 MVC 서블릿과 같다. 코드를 보자.

hello.servlet.web.frontcontroller.v1.controller.MemberFormControllerV1

1
2
3
4
5
6
7
8
9
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);  
    }  
}

implements ControllerV1 우리가 만든 인터페이스를 상속 시켰고, 그 안에 process 메서드를 구현하는데, 구현 자체는 이전 시간에 한 코드와 동일하다.

hello.servlet.web.frontcontroller.v1.controller.MemberListControllerV1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);  
    }  
}

hello.servlet.web.frontcontroller.v1.controller.MemberSaveControllerV1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MemberSaveControllerV1 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);  
    }  
}

이렇게 3개의 컨트롤러를 만들었고 이제 프론트 컨트롤러만 남았다.

프론트 컨트롤러는 다음 위치에 만들 거다.

v1 에 만들면 된다.

자 여기 서는 이전에 한 것처럼 서블릿을 만들어야 한다.

hello.servlet.web.frontcontroller.v1.FrontControllerServletV1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@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 MemberSaveControllerV1());  
        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);  
    }  
}

자 하나하나 보자.

먼저

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

클래스 선언부 에서는 extends HttpServlet 상속 받고

urlPatterns = "/front-controller/v1/*"

url 패턴이 다음과 같다.

이는 즉 /front-controller/v1/ 이 뒤에 뭐가 들어오던 일단 이 컨트롤러가 처리 하겠다는 것이다.

1
2
3
4
5
6
7
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 MemberSaveControllerV1());  
	controllerMap.put("/front-controller/v1/members", new MemberListControllerV1());  
}  

한 줄 한 줄 보자면, 먼저 Map<String, ControllerV1> 자료구조를 만들어 놨다.

그리고 생성자를 선언 해 놨는데,

서버가 돌아갈 때 서블릿이 생성자를 호출 하기 때문에 이를 이용했다.

잘 보면

1
2
3
controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());  
controllerMap.put("/front-controller/v1/members/save", new MemberSaveControllerV1());  
controllerMap.put("/front-controller/v1/members", new MemberListControllerV1()); 

키가 URL 패턴이고, 값이 우리가 만들어 놓은 컨트롤러가 들어가 있는 걸 볼 수 있다.

이제 service 메서드를 잘 구현해서 각각 컨트롤러로 보내주면 될 거 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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);  
}

service 메서드를 보면 먼저 String requestURI = request.getRequestURI(); 이 코드로 어떤 url이 들어왔는지 파악을 한다.

예를 들어 http://localhost:8080/front-controller/v1/members/new-form 이런 식으로 들어오면

String requestURI = /front-controller/v1/members/new-form 가 된다.

그 다음에 먼저 만들어 놓은 자료구조의 키 값으로 저걸 사용해서

ControllerV1 controller = controllerMap.get(requestURI); 다음과 같이 컨트롤러 객체를 반환 받는다.

우리가 인터페이스로 ControllerV1 을 만들어 놨기 때문에, 상속 받은 자식 클래스들은 다 담을 수 있다.

1
2
3
4
if (controller == null) {  
	response.setStatus(HttpServletResponse.SC_NOT_FOUND);  
	return;  
} 

혹시 컨트롤러가 없을 수도 있어서 404 보내주기 위한 처리도 해줬다.

마지막으로

1
controller.process(request, response);  

컨트롤러의 메서드를 실행 시켜주면 끝난다.

자 이제 보자.

폼 잘뜬다.

저장도 잘 된다.

리스트도 잘 나온다.

view는 이전에 쓰던 걸 재활용 했다.

없는 url을 입력하면 404가 발생하게 된다.

이로써

이 구조를 만들어 봤다.

댓글남기기