본문 바로가기

[Spring] FrontController 도입하기 V2 - 포워딩 중복 제거

@xuv22025. 6. 23. 11:31

https://bdisappointed.tistory.com/154

 

[Spring] FrontController 도입하기 V1

[Spring] 서블릿과 JSP로 MVC 흉내내기 , 그리고 한계점서블릿과 JSP를 통해 MVC를 구현 해볼 예정이다. 구현은 다음과 같다.서블릿 : 컨트롤러JSP : 뷰HttpServletRequest 가 제공하는 내부 저장소 (Attribute) :

bdisappointed.tistory.com

이전 글에 이어 추가로 리팩토링 해보겠다.


프론트 컨트롤러 도입 - V2

String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);

V1에서는 컨트롤러 -> 뷰로 포워딩하는 과정의 코드가 계속 중복이 되었는데, 이번에는 MyView 라는 객체를 통해 이를 해결해보자

 


MyView & FrontController V2

package hello.servlet.web.frontcontroller;

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.Map;

public class MyView {
    private String viewPath; // /WEB-INF/views/save-result.jsp

    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);
    }
}

 

package hello.servlet.web.frontcontroller.v2;

import hello.servlet.web.frontcontroller.MyView;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public interface ControllerV2 {

    MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

}

 

이번에는 각 컨트롤러가 MyView를 반환하도록 하겠다.

또한, dispatcher.foward() 메서드도 더이상 사용하지 않아도 된다. 자세한 것은 프론트 컨트롤러 코드를 보면 이해할 수 있다.


컨트롤러

회원 등록

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

import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v2.ControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class MemberFormControllerV2 implements ControllerV2 {

    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        return new MyView("/WEB-INF/views/new-form.jsp");
    }
}

 

회원 저장

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

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v2.ControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class MemberSaveControllerV2 implements ControllerV2 {

    private 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);

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

        return new MyView("/WEB-INF/views/save-result.jsp");
    }
}

 

회원 조회

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

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v2.ControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

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

public class MemberListControllerV2 implements ControllerV2 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        List<Member> members = memberRepository.findAll();
        request.setAttribute("members", members);

        return new MyView("/WEB-INF/views/members.jsp");
    }
}

 


프론트 컨트롤러 

package hello.servlet.web.frontcontroller.v2;

import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.v2.controller.MemberFormControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberListControllerV2;
import hello.servlet.web.frontcontroller.v2.controller.MemberSaveControllerV2;
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;

import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;

@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {

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

    public FrontControllerServletV2() {
        controllerMap.put("/front-controller/v2/members/new-form", new MemberFormControllerV2());
        controllerMap.put("/front-controller/v2/members/save", new MemberSaveControllerV2());
        controllerMap.put("/front-controller/v2/members", new MemberListControllerV2());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();

        // 다형성 활용
        ControllerV2 controller = controllerMap.get(requestURI);

        // 만약 없으면?
        if (controller == null) {
            response.setStatus(SC_NOT_FOUND);
            return;
        }

        // 얘가 이제 MyView를 반환한다
        MyView view = controller.process(request, response);
        view.render(request,response);
    }
}

참고로, 컨트롤러 등이 맵에 보관되는 생성자는 서블릿 컨테이너에 서블릿이 등록 될 때, 자동으로 실행된다.

 

동작 과정

본론으로 넘어가, 우리는 기존 컨트롤러들에서 포워딩하는 로직을 MyView 객체로 떼어냈다.

구체적인 과정은 , /front-controller/v2/xxxx 로 들어오는 모든 요청을 먼저 프론트 컨트롤러에서 처리한다.

프론트 컨트롤러에서는 getURI를 통해 컨트롤러를 찾아내고, 해당 컨트롤러에서 비즈니스 로직을 수행한다 (process() 메서드). 열심히 비즈니스 로직을 수행 한 후에 우리가 만들었던 MyView 객체를 반환한다. (이때 MyView 인스턴스는 포워딩 대상 JSP 파일의 경로를 가지고 있다).

반환된 MyView 객체를 통해 render() 메서드를 수행하면 포워딩 대상 경로로 포워딩을 실행하여 JSP 파일을 호출한다.

 


결론

이번에는 각 컨트롤러에서 중복되던 포워드 로직을 MyView객체를 통해 render() 메서드를 만들어 해결했다.

다음 V3에서는 서블릿 종속성을 제거해보겠다.

xuv2
@xuv2 :: xuvlog

폭싹 늙었수다

목차