스프링

[ Spring ] 고대의 서블릿을 찾아서(5) FrontController에 View 추가

do_it_zero 2024. 10. 14. 09:07

지금까지의 고대의 서블릿 찾는 여정...
서블릿 -> 템플릿 엔진 -> mvc패턴 -> FrontController 도입
전에 있던 단점을 보안하며 FrontController까지 오게되었다.

왜 view가 필요할까?

전에 있던 단점들은 대부분 중복되는 코드들을 어떻게 하면 공통으로 쓸 수 있을까? 에 대한 고민이였다. 이번 view를 추가하는 것도 동일하다.

FrontController의 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";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}

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);
        System.out.println("member = " + member);
        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);
    }
    
}

public class MemberListControllerV1 implements ControllerV1{
    
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
        System.out.println("MvcMemberListServlet.service");
        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);

    }
}

controller마다 중복되는 코드가 보인다! 그것은 바로 viewPath에 해당하는 jsp 서블릿을 찾아서 데이터를 넘겨주는 부분이다.

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

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

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

중복되는 코드들을 해결하기 위해 MyView 객체를 만든다.

public class MyView {
    
    private String viewPath;

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

이 때 FrontController에서 MyView라는 객체의 역할을 명확하게 나타내기 위해서 Controller 인터페이스의 반환값을 변경한 ControllerV2 인터페이스를 만든다.

public interface ControllerV2 {
    MyView process(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException; 
}

Q.왜 ControllerV2가 MyView를 반환하게 만들어야할까?

이 부분을 좀 더 자세히 설명하자면 다음과 같다.
MyView를 ControllerV1의 인터페이스를 상속 받은 controller에 적용시킨다면 다음과 같다.

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);
        System.out.println("member = " + member);
        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);
        // MyView 적용
        MyView view = new MyView(viewPath);
        view.render(request, response);
    }
    
}

controller 내부에서 view.render까지 진행해도 된다. 하지만 이럴 경우에는 각 controller마다 view.render까지 코드를 작성해야한다. 즉, 중복 코드를 없애기 위해 MyView를 만들었는데 다시 중복 코드가 생기는 것이다. 또한 FrontController에서 MyView의 역할을 명확하게 알 수 없다. 따라서 MyView를 반환하는 ControllerV2 인터페이스를 만드는 것이다.

MyView를 반환하는 ContollerV2와 ContollerV2를 상속 받는 controller들

public interface ControllerV2 {
    MyView process(HttpServletRequest request,HttpServletResponse response) throws ServletException,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");
    }
}

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);
        System.out.println("member = " + member);
        memberRepository.save(member);

        // Model에 데이터를 보관
        request.setAttribute("member", member);
        
        return new MyView("/WEB-INF/views/save-result.jsp");
    }   
}

public class MemberListControllerV2 implements ControllerV2{
    
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
        System.out.println("MvcMemberListServlet.service");
        List<Member> members = memberRepository.findAll();

        request.setAttribute("members", members);

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

각 contoller들을 반환값으로 jsp 경로가 있는 MyView 인스턴스를 반환한다. 이제 반환된 MyView 인스턴스가 render를 하도록 만들면된다. 위에서 언급한 것처럼 이 부분은 공통이기 때문에 FrontController에서 진행하면 된다.

@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 {
        System.out.println("FrontControllerServletV2.service");
        String requestURI = request.getRequestURI();
        ControllerV2 controller = controllerMap.get(requestURI);
        
        if(controller == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }

        MyView view = controller.process(request, response);
        view.render(request, response);
    }
}

이렇게 해서 FrontController에서는 jsp 서블릿으로 데이터를 넘겨주는 공통 처리 기능인 render까지 할 수 있게 되었다.

정리

전에 강의를 들으면서 곰곰히 생각하지 않았던 부분들이 있었다. 그냥 그렇게 하라니깐 코드를 작성했는데, 복습하고 정리하면서 '왜 이 코드를 여기서 작성하는거지?' 라는 질문을 하면서 생각하는 시간을 가졌고 코드를 이해하는 정도가 깊어진 것 같다. 궁금한 것이 있으면 끝까지 생각해보고 스스로 질문을 해야겠다.

참고
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1