코드 그라데이션

View 분리 - v2 본문

Spring/MVC 1

View 분리 - v2

완벽한 장면 2023. 9. 25. 23:23

View 분리 - v2

모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있고, 깔끔하지 않다.

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


이 부분을 깔끔하게 분리하기 위해 별도로 뷰를 처리하는 객체를 만든다.

 

V2 구조

 

 

MyView

뷰 객체는 이후 다른 버전에서도 함께 사용하므로 패키지 위치를 frontcontroller 에 두었다.

public class MyView {
// 기존에 컨트롤러에서 했던 로직을 MyView 만들어서 분리해서 넣음.
  private String viewPath; // => new MyView("/WEB-INF/views/new-form.jsp") 뷰 페이지의 경로를 저장

  // 생성자
  public MyView(String viewPath) {
    this.viewPath = viewPath; // 생성자를 통해 뷰 페이지의 경로를 설정
  }

	/**
     * 요청된 내용을 해당 뷰 페이지로 포워딩하여 렌더링합니다.
     * @param request HTTP 요청 객체입니다.
     * @param response HTTP 응답 객체입니다.
     * @throws ServletException 서블릿 예외가 발생할 수 있습니다.
     * @throws IOException 입출력 예외가 발생할 수 있습니다.
     */
  public void render (HttpServletRequest request, HttpServletResponse response) 
  	throws ServletException, IOException {
    
    RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
    dispatcher.forward(request, response); // 요청을 지정한 뷰 페이지로 전달하여 렌더링
  }

    /**
     * 전달된 모델 데이터를 요청 객체의 속성으로 설정하고, 
     * 해당 뷰 페이지로 포워딩하여 렌더링합니다.
     * @param model 뷰에 전달할 데이터를 담고 있는 맵 객체입니다.
     * @param request HTTP 요청 객체입니다.
     * @param response HTTP 응답 객체입니다.
     * @throws ServletException 서블릿 예외가 발생할 수 있습니다.
     * @throws IOException 입출력 예외가 발생할 수 있습니다.
     */
  public void render(Map<String, Object> model, 
  	HttpServletRequest request, HttpServletResponse response) 
    	throws ServletException, IOException {
    
    modelToRequestAttribute(model, request); // 모델 데이터를 요청 속성으로 설정
    RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
    dispatcher.forward(request, response); // 요청을 지정한 뷰 페이지로 전달하여 렌더링
  }
  
     /**
     * 모델 데이터의 내용을 요청 객체의 속성으로 설정합니다.
     * @param model 뷰에 전달할 데이터를 담고 있는 맵 객체입니다.
     * @param request HTTP 요청 객체입니다.
     */

  private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
  
  	// 모델 데이터의 각 항목을 요청 객체의 속성으로 설정
    model.forEach((key, value) -> request.setAttribute(key, value));
  }
}

 

  • 이 클래스는 컨트롤러와 뷰 사이의 관심사 분리를 도와주며, 뷰 렌더링 관련 로직을 중앙에서 관리하고 재사용할 수 있도록 돕는 역할을 수행

 

ControllerV2

public interface ControllerV2 {

    // 기존 ControllerV1과 같은데 반환만 MyView를 한다.
    // 기존에는 void였고 컨트롤러가 알아서 다 forward 이동했는데 
    // 그냥 MyView를 만들어서 넘기면 되는 식으로 인터페이스를 설계
    MyView process(HttpServletRequest request, HttpServletResponse response) 
    	throws ServletException, IOException;
}
  • 웹 요청을 처리하고 뷰 객체(MyView)를 반환하는 역할을 수행
  • process 메서드 : 웹 애플리케이션의 비즈니스 로직을 처리하고, 결과로 MyView 객체를 반환
  • ControllerV2에서는 뷰 페이지의 경로 등을 직접 처리하는 것이 아니라, MyView 객체를 반환
  • 이렇게 함으로써 컨트롤러는 뷰 렌더링과 관련된 로직을 다루지 않고, 단순히 비즈니스 로직 처리에 집중 가능
  • MyView 객체를 반환함으로써, 뷰 렌더링과 관련된 처리를 MyView 클래스 내에서 처리할 수 있게 됨.
  • 컨트롤러와 뷰 간의 역할 분담과 관심사 분리가 이루어지며, 유지보수성과 확장성이 향상됨.

 

MemberFormControllerV2 - 회원 등록 폼

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

/*
//        //변수 인라인화 시켜서 깔끔하게
//        MyView myView = new MyView("/WEB-INF/views/new-form.jsp");
//        return myView; 
// => return new MyView("/WEB-INF/views/new-form.jsp");
*/
  • 위의 코드에서는 MyView 객체를 생성하고, 해당 뷰 페이지의 경로를 생성자에 전달하여 초기화.
  • 이렇게 생성된 MyView 객체를 반환하면, 이후에 뷰 렌더링이 필요할 때 해당 객체를 사용할 수 있게 된다.
  • MyView 객체를 반환하므로써 컨트롤러의 역할은 비즈니스 로직 처리에만 집중하며, 실제 뷰 렌더링에 대한 로직은 MyView 클래스 내부에서 처리됨.
    위의 코드는 "회원 가입 폼을 보여주는 페이지" 컨트롤러로, new-form.jsp라는 뷰 페이지로 포워딩.                            이렇게 하면 해당 뷰 페이지가 사용자에게 보인다.

 

MemberSaveControllerV2 - 회원 저장

public class MemberSaveControllerV2 implements ControllerV2 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
            
        // HTTP 요청에서 사용자명과 나이를 파라미터로 받는다.
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        // 받은 정보로 Member 객체를 생성.
        Member member = new Member(username, age);
        
        // MemberRepository를 사용하여 회원 정보를 저장합니다.
        memberRepository.save(member);

        // 저장된 회원 정보를 request 객체의 속성으로 저장.
        // 모델에 데이터 저장 => 이렇게 하면 뷰 페이지에서 해당 정보를 사용할 수 있음
        request.setAttribute("member", member);

        // 뷰 페이지 경로를 설정하고 해당 경로로 포워딩할 MyView 객체를 반환.
        /*
        String viewPath = "/WEB-INF/views/save-result.jsp";
		RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
	    dispatcher.forward(request,response);
		위의 세줄이 아래 한줄 코드
        */
        return new MyView("/WEB-INF/views/save-result.jsp");
        // => 이렇게 하면 사용자에게 회원 저장 결과를 보여주는 페이지로 이동.
    }
}

 

 

MemberListControllerV2 - 회원 목록

public class MemberListControllerV2 implements ControllerV2 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
            
        // MemberRepository를 통해 모든 회원 정보를 조회.
        List<Member> members = memberRepository.findAll();
        
        // 조회한 회원 정보를 request 객체의 속성으로 저장.
        request.setAttribute("members", members);

        // 뷰 페이지 경로를 설정하고 해당 경로로 포워딩할 MyView 객체를 반환.
        return new MyView("/WEB-INF/views/members.jsp");
    }
}

 

 

서블릿 만들기

FrontControllerServletV2

  • 서블릿(Servlet)으로, 웹 애플리케이션의 프론트 컨트롤러(Front Controller) 역할을 하는 클래스.
  • 이 클래스는 URL에 따라 적절한 컨트롤러를 호출하고, 컨트롤러의 결과를 뷰(View)로 렌더링하여 클라이언트에 응답을 제공함
@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {

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

    public FrontControllerServletV2() {
        // 서블릿 초기화 시에 각 URL 패턴에 맞는 컨트롤러를 매핑.
        // ex. url = /front-controller/v2/members/new-form 이면 * 에 걸린다
        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 {
            
        // 클라이언트 요청의 URI를 가져온다.
        String requestURI = request.getRequestURI();

        // 요청 URI에 해당하는 컨트롤러를 매핑된 컨트롤러 맵에서 찾는다.
        // controllerMap은 URI와 컨트롤러 클래스를 매핑하는 맵. 
        // 서블릿이 초기화될 때, 
        // 서블릿 컨텍스트에서 정의한 URL 패턴에 따라 각 컨트롤러 클래스를 매핑
        ControllerV2 controller = controllerMap.get(requestURI);

        if (controller == null) {
            // 컨트롤러를 찾을 수 없을 경우 404 상태 코드를 반환하고 종료.
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // 해당 컨트롤러를 실행하고 결과로 MyView 객체를 받음.
        // 이제 반환을 하는 게 살짝 차이
        // 생성한 결과 : new MyView("/WEB-INF/views/new-form.jsp")
        MyView view = controller.process(request, response);

        // MyView를 사용하여 뷰를 렌더링하고 클라이언트에 응답을 전송.
        view.render(request, response);
    }
}
  • MyView 객체는 뷰 페이지의 경로와 모델 데이터를 관리

 

 

실행

등록: http://localhost:8080/front-controller/v2/members/new-form
목록: http://localhost:8080/front-controller/v2/members

 

데이터 입력하면

 

결과 출력되는 것까지 확인 가능

728x90

'Spring > MVC 1' 카테고리의 다른 글

단순하고 실용적인 컨트롤러 - v4  (0) 2023.09.26
Model 추가 v3  (0) 2023.09.26
프론트 컨트롤러 도입 - v1  (0) 2023.09.24
프론트 컨트롤러 패턴 소개  (0) 2023.09.24
MVC 패턴 - 한계  (0) 2023.09.23
Comments