SPRING/Spring MVC

[SpringMVC] MVC 패턴 구현하기(3) - Model

IT록흐 2023. 8. 9. 15:53
반응형

 

 

[SpringMVC] MVC 패턴 구현하기(1) - View

[SpringMVC] 응답(Response)의 종류 ( Text, Html, Json ) [SpringMVC] 요청(Request)의 종류 ( GET, POST, JSON ) [SpringMVC] 웹서비스에서 Request(요청)가 처리되는 원리 ( + Servlet ) JAVA Runtime Enviroment(JRE)는 하나의 프로세스

lordofkangs.tistory.com

 

[SpringMVC] MVC 패턴 구현하기(2) - Controller

[SpringMVC] MVC 패턴 구현하기(1) - View 분리하기 [SpringMVC] 응답(Response)의 종류 ( Text, Html, Json ) [SpringMVC] 요청(Request)의 종류 ( GET, POST, JSON ) [SpringMVC] 웹서비스에서 Request(요청)가 처리되는 원리 ( + Servl

lordofkangs.tistory.com

 

 

지난 포스팅까지 MVC 패턴에서 View와 Controller를 구현해보았다. 이번 포스팅에서는 Model을 구현해보겠다. 

 

 

Controller

public class MemberSaveController implements Controller {
    MemberRepository memberRepository = MemberRepository.getInstance();
 
    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // [ 비즈니스 로직 ]
        String username = request.getParameter("username"); //request 객체에 의존!
        int age =Integer.parseInt(request.getParameter("age")) ;
 
        Member member =new Member(username,age);
        memberRepository.save(member);
 
        // [ 결과 반환하기 ]
        request.setAttribute("member",member); //request 객체에 의존!
        return new MyView("/WEB-INF/views/save-result.jsp"); 
    }
 
}

 

지난 포스팅에서 구현한 Controller 코드이다. 여기서 문제는 Controller가 Request, Response객체에 의존하고 있다는 점이다. 심지어 Response는 이미 View영역으로 분리되어 더이상 신경쓰지 않음에도 매개변수로 사용되고 있다.

 

 

 

 

Request 객체로부터 요청데이터를 가져와 비즈니스 로직을 처리하고 그 결과를 다시, Request 객체로 보낸다. Controller는 비즈니스로직을 처리하는 클래스이다. 그러므로 비즈니스 로직과 관련된 객체에만 의존하는 것이 좋다. Request 객체는 비즈니스 로직 외에도 Header, Cookie, Session 등 여러 복잡한 데이터를 품고 있는 데이터이다. 

 

 

 

 

Request와 Controller를 분리하려면, 비즈니스 로직 관련 데이터를 Map 자료구조에 따로 저장하여 Controller에 보내면 된다. 그럼 Controller는 Request가 아닌 Map에 의존하니 오로지 비즈니스 로직으로 응집된 클래스가 된다.  Request와 Controller 사이의 작업은 Front-Servlet이 담당한다. 프론트 서블릿은 비즈니스 로직을 제외한 공통작업을 담당한다.

 

Front-Servlet

@WebServlet(name = "frontControllerServlet",urlPatterns = "/front-controller/*")
public class FrontControllerServlet extends HttpServlet {

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

    public FrontControllerServlet() {
        controllerMap.put("/front-controller/members/new-form",new MemberFormController());
        controllerMap.put("/front-controller/members/save",new MemberSaveController());
        controllerMap.put("/front-controller/members",new MemberListController());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // [ STEP1. 요청된 Controller 탐색하기 ]
        String requestURI = request.getRequestURI();
        Controller controller = controllerMap.get(requestURI);

        // 요청된 컨트롤러가 없을 시, 404 에러 발생
        if(controller == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // [ STEP2. 요청 데이터, 응답 데이터 생성하기  ]
        Map<String,String> requestModel = createParamMap(request,controller); // 요청 데이터용 Map 데이터
        Map<String,Object> responseModel = new HashMap<>(); // 응답 데이터용 Map 데이터

        // [ STEP3. 비즈니스 로직 처리후 결과 반환하기  ]
        String viewName = controller.process(requestModel, responseModel); // request와 response 대신에 requestModel, responseModel이 Controller로 전달

        // [ STEP4. VIEW 영역에 데이터 전달하기  ]
        MyView view = viewResolver(viewName); // JSP 경로 생성
        view.render(responseModel,request,response); // VIEW 영역에 데이터 전달
    }

    private MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp"); // 정해진 위치에 모여있는 JSP 파일
    } 

    // Request에서 요청 데이터 추출하기
    private Map<String,String> createParamMap(HttpServletRequest request, Controller controller) {
        Map<String,String> paramMap = new HashMap<>();
        request.getParameterNames().asIterator().forEachRemaining(paramName-> paramMap.put(paramName, request.getParameter(paramName))); // 요청으로 들어온 데이터 모음
        return paramMap;
    }
}

 

위 코드에서 Front-Servlet은 4가지 순서로 작업을 한다. 

 

STEP1. 요청된 url과 매핑되는 Controller 찾기 

STEP2. 요청 데이터 Map, 응답 데이터 Map 생성하기 

STEP3. Controller에게 데이터 전달하여 비즈니스 로직 처리하고 결과 전달 받기

STEP4. 결과 데이터를 VIEW 영역에 전달하기 

 

STEP2를 보면 Request 객체에서 요청 데이터를 추출하여 Map구조에 저장하는 것을 볼 수 있다. 이렇게 생성된 Map 데이터가 Model이다. Model은 요청과 응답에 비즈니스 로직 데이터를 전달하는 역할을 한다.

 

Controller

public class MemberSaveController implements Controller {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> requestModel,Map<String,Object> responseModel) {
        
        // [ 비즈니스 로직 ]
        String username = requestModel.get("username"); // 요청 데이터 가져오기
        int age = Integer.parseInt(requestModel.get("age"));

        Member member = new Member(username,age);
        memberRepository.save(member);

        // [ 응답데이터 설정 ]
        responseModel.put("member",member); // 응답 데이터 설정
        return "save-result"; // View 리졸버에 전달할 View name 반환

    }
}

 

 

Controller을 보면 비즈니스 로직을 처리하기 위해 requestModel에서 데이터를 가져온다. 비즈니스 로직이 처리되면 responseModel에 응답데이터를 저장하고 View 리졸버에 전달할 View name을 반환한다.  응답데이터를 View 영역에 전달하려면 Reqeust 객체에 응답데이터를 세팅해야 한다. 프론트 서블릿 STEP4의 view.render 메소드의 코드를 자세히 보자. 

 

MyView

public class MyView {
    private String viewPath;

    public MyView(String viewPath){
        this.viewPath = viewPath;
    }

    public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        modelToRequestAttribute(model, request); //Request에 응답데이터 저장하기
        
        //View 영역으로 데이터 전달하기
        RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewPath);
        requestDispatcher.forward(request,response);
    }

    private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
        model.forEach((key, value) -> request.setAttribute(key,value)); //Model에서 가져온 데이터 Request에 저장하기
    }
}

 

JSP 같은 VIEW 영역에서 Response 객체를 설정하여 클라이언트에게 전달하므로, 서블릿은 응답에 필요한 데이터를 Request 객체에  설정해놓아야 한다. 비즈니스 로직을 처리한 결과를 Model에 담아 MyView 클래스의 render 메소드에 전달하면, model의 데이터를 Request에 설정한다. 그리고 RequestDispatcher를 통해 VIEW 영역으로 응답데이터가 전달된다. 

 

 

 

 

 

정리하면,

 

처음에는 화면을 동적으로 그리는 기능, 비즈니스 로직, 요청 및 응답 데이터가 하나의 서블릿에 들어가 있었다. 그래서 처음에는 JSP, Thymleaf 같은 HTML을 동적으로 생성하는 환경을 두어 View 영역을 분리하였다. View영역을 분리하였으나 View 영역에 데이터를 전달하기 위한 MyView 코드가 서블릿마다 중복되어 생성되었다. 그래서 프론트 컨트롤러 패턴을 도입하여, 공통업무 로직은 프론트 서블릿에, 비즈니스 로직은 컨트롤러로 분리하여 중복코드를 줄이고 성능을 최적화 하였다. 그러나 Request와 Response는 덩치가 크다. 덩치가 큰 Request와 Response가 매개변수로 돌아다니면 응집도가 떨어진다. 비즈니스 로직 관련 데이터만 매개변수로 전달 될 수 있도록 Model 객체를 만들었다. 이로써 시스템의 응집도는 높아지게 되었다.

 

여기까지가 서블릿에서 MVC(Model-View-Controller) 패턴을 구현한 모습이다. 다음 포스팅에서는 MVC 패턴의 성능을 더욱 최적화 시킬 수 있도록 Adapter를 적용해보겠다. 

 

 


 

참고자료

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

www.inflearn.com

 

반응형