들어가며
- 저번 포스트에서 프론트 컨트롤러 구현을 시작하였다.
- 먼저 회원 데이터를 저장하고 보여주는 간단한 서비스를 만들기 위해 Domain, Repository를 작성하였다. 이번 포스트에서는 각 컨트롤러 구현을 수행할 것이다.
- 모든 진행 결과는 필자의 깃허브에서 볼 수 있다.
프론트 컨트롤러 구현 - 계속
컨트롤러 구현
- 이전 포스트에서도 적었지만 프론트 컨트롤러가 각 컨트롤러의 정보를 알고 있는 상태에서 클라이언트의 요청을 처리할 수 있는 컨트롤러에 요청을 위임한다고 하였다.
- 그리고 각 컨트롤러의 정보는 <컨트롤러의 URL Mapping, 컨트롤러 객체>를 가진 Map이다. 컨트롤러에 매핑된 URL은 String 형식으로 할 수 있지만 객체의 경우에는 각 클래스들로 타입이 다르기 때문에 저장이 힘들어 보인다.
- 이럴때 사용할 수 있는 것이 일반화(Generalization)이다.
- 각 컨트롤러 객체들은 처리하는 방식은 다르지만 "들어온 요청을 처리한다" 라는 책임은 동일하게 지고 있다. 그렇다면 이 객체들을 추상화 시켜 "들어온 요청을 처리하는 컨트롤러 역할"로 일반화시킬 수 있다.
- 자바에서는 이를 인터페이스로 구현하거나, 추상 클래스로 구현할 수 있다. 필자는 인터페이스를 통해 구현할 것이다.
- 이렇게 인터페이스로 일반화시킨다면 다형성을 활용하여 각 객체들을 Mapping Store에 저장시킬 수 있다.
public interface ControllerV1{
void process(
HttpServletRequest request,
HttpServletResponse response
) throws ServletException, IOException;
}
- 이후 controller 패키지를 만들고 그 내부에 컨트롤러 구현체들을 만들 것이다.
public class MemberFormControllerV1 implements ControllerV1 {
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String viewPath = "/WEB-INF/views/new-form.jsp";
request.getRequestDispatcher(viewPath).forward(request, response);
}
}
public class MemberListControllerV1 implements ControllerV1 {
private static final 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";
request.getRequestDispatcher(viewPath).forward(request, response);
}
}
public class MemberSaveControllerV1 implements ControllerV1 {
private static final MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userName = request.getParameter("username");
int userAge = Integer.parseInt(request.getParameter("age"));
Member member = new Member(userName, userAge);
memberRepository.save(member);
request.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
request.getRequestDispatcher(viewPath).forward(request, response);
}
}
- Controller Interface를 구현한 구현체 컨트롤러들이다.
프론트 컨트롤러 구현
- 뒷단에서 요청을 처리할 컨트롤러들을 구현하였고 이제 남은건 앞에서 모든 요청을 받는 프론트 컨트롤러를 구현하는 것만 남았다.
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
private final 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("FrontControllerV1.service");
String requestURI = request.getRequestURI();
System.out.println(requestURI);
ControllerV1 controllerV1 = controllerMap.get(requestURI);
if (controllerV1 == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
controllerV1.process(request, response);
}
}
- /front-controller/v1/*의 urlPatterns는 해당 경로로 들어오는 모든 요청에 대하여 이 프론트 컨트롤러가 호출된다는 것이다.
- controllerMap은 이전에 말한 것 처럼 URL을 key로, 컨트롤러 객체를 value로 하는 Map이다. ControllerV1 인터페이스를 통하여 하나의 타입으로 일반화시켜 관리가 가능하다.
- service에서는 다음과 같은 동작을 수행한다.
- 먼저 들어온 요청의 URI를 확인한다.
- 이후 controllerMap에서 해당 URI를 key로 넣어 대응되는 컨트롤러가 존재하는지 확인한다.
- 만약 찾지 못했다면 해당 리소스를 찾지 못했다는 상태 응답인 404(NOT_FOUND)를 반환시키고 종료한다.
- 찾았다면 해당 컨트롤러에 요청을 위임시킨다.
- 만약 공통 로직을 처리해야 한다면 프론트 컨트롤러에 공통 로직을 구현하면 된다.
- 이로서 새로운 컨트롤러를 구현해도 controllerMap에 URI와 객체를 넣음으로서 공통 로직을 새로운 컨트롤러에 구현해야 할 필요가 사라졌다.
정리
- 전체적인 프론트 컨트롤러 구현을 살펴보았다. 이번 과정에서는 프론트 컨트롤러를 도입만 수행하였고 추가적인 변경은 수행하지 않았다.
- 다음 포스트에서는 뷰(View)를 별도로 분리하여 관리하도록 유도할 것이다. 그렇게 함으로서 어떤 이득이 들어오는지를 알아본다.
'Spring & JPA > SpringMVC' 카테고리의 다른 글
Spring MVC - MVC 구조 - 5. Model의 추가 (0) | 2023.06.01 |
---|---|
Spring MVC - MVC 구조 - 4. View의 분리 (1) | 2023.05.31 |
Spring MVC - MVC 구조 - 2. 프론트 컨트롤러 구현(1) (0) | 2023.05.30 |
Spring MVC - MVC 구조 - 1. 프론트 컨트롤러 (0) | 2023.05.30 |
Spring MVC - 기반 지식 - 4. WAS와 리소스 반환, CSR, SSR (0) | 2023.05.26 |