본문 바로가기

Spring/Spring Quick Start

MVC를 직접 구현하여 게시판 개발

Spring Quick Start(책)의 Day3 class04 실습을 진행하면서 기록한 내용입니다.


Spring MVC를 적용하기 전, 동일한 구조로 프레임워크를 직접 구현해보려고 한다.

MVC 프레임워크 구조는 아래와 같다.

🧀 로그인 기능 동작 과정 🧀

 

1. 클라이언트가 로그인 버튼을 클릭하여 "/login.do" 요청을 전송하면 DispatcherServlet이 요청을 받는다.

2. DispatcherServlet은 HandlerMapping 객체를 통해 로그인 요청을 처리할 LoginController를 검색하고,

3. 검색된 LoginController의 handelRequest() 메소드를 호출하면 로그인 로직이 처리된다.

4. 로그인 처리 후에 이동할 화면 정보가 리턴되면

5. DispatcherServlet은 ViewResolver를 통해 접두사와 접미사가 부튼 JSP 파일의 이름과 경로를 리턴받는다.

6. 그리고 최종적으로 JSP를 실행하고 실행결과가 브라우저에 응답된다.

 


상세한 코드를 작성하는 과정이다.

(1) Controller 인터페이스 작성

클라이언트의 요청을 받은 DispatcherServlet은 HandlerMapping을 통해 Controller 객체를 검색하여 실행한다.

이때 어떤 Controller 객체가 검색되더라도 같은 코드로 실행하려면, 모든 Controller의 최상위 인터페이스가 필요하다.

package com.springbook.view.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface Controller {
	String handleRequest(HttpServletRequest request, HttpServletResponse response);
}

 

(2) LoginController 구현

handlerRequest() 오버라이드!

package com.springbook.view.user;

public class LoginController implements Controller {
    @Override
    pubic String handleRequest(HttpServletRequest request, HttpServletResponse response) {
        
        System.out.println("로그인 처리");
        
        // 1. 사용자 입력 정보 추출
        String id = request.getParameter("id");
        String password = request.getParameter("password");
        
        // 2. DB 연동 처리
        UserVO vo = new UserVO();
        vo.setId(id);
        vo.setPassword(password);
        
        UserDAO userDAO = new UserDAO();
        UserVO user = userDAO.getUser(vo);
        
        // 3. 화면 네비게이션
        if (user != null) {
            return "getBoardList.do";
        } else {
            return "login";
        }       
    }                     
}

 

(3) HandlerMapping 클래스 작성

HandlerMapping은 모든 Controller 객체들을 저장하고 있다가, 클라이언트의 요청이 들어오면 요청을 처리할 특정 Controller를 검색하는 기능을 제공한다.

package com.springbook.view.controller;

import java.util.HashMap;
improt java.util.Map;

improt com.springbook.view.user.LoginController;

public class HandelrMapping {
    private Map<String, Controller> mappings;
    
    public HandlerMapping() {
        mappings = new HashMap<String, Controller>();
        mappings.put("/login.do", new LoginController());
    }
    
    public Controller getController(String path) {
        return mappings.get(path);
    }
}

 

(4) ViewResolver 클래스 작성

ViewResolver 클래스는 Controller가 리턴한 View 이름에 접두사(prepix)와 접미사(suffix)를 결합하여 최종으로 실행될 View 경로와 파일명을 완성한다. 

package com.springbook.view.controller;

public class ViewResolver {
    public String prefix;
    public String suffix;
    
    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
    
    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
    
    public String getView(String viewName) {
        return prefix + viewName + suffix;
    }
}

ViewResolver도 HandlerMapping과 마찬가지로 DispatcherServlet의 inti() 메소드가 호출될 때 생성한다.

 

(5) DispatcherServlet 수정

서블릿의 inti() 메소드는 서블릿 객체가 생성된 후에 멤버변수를 초기화하기 위해 자동으로 실행된다.

따라서 init() 메소드에서 DispatcherServlet이 사용할 HandlerMapping와 ViewResolver 객체를 초기화한다. process() 메소드는 위의 과정을 이용하여 처리하도록 수정되었다.

package com.springbook.view.controller;

public class DispathcerServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private handlerMapping handlerMapping;
    private ViewResolver viewResolver;
    
    public void init() throws ServletExcetpion {
        handlerMapping = new HandlerMapping();
        viewResolver = new Resolver();
        viewResolver.setPrefix("./");
        viewResolver.setSuffix(".jsp");
    }
   
    private void process(HttpServletRequest request, HttpServletResponse response)
                           throws IOException {
        // 1. 클라이언트의 요청 path 정보를 추출한다.
        String uri = reqeust.getRequestURI();
        String path = uri.substring(rui.lastIndexOf("/"));
       
        // 2. HandlerMapping을 통해 path에 해당하는 Controller를 검색한다.
        Controller ctrl = hanlderMapping.getController(path);
       
        // 3. 검색된 Controller를 실행한다.
        String viewName = ctrl.handlerRequest(request, response);
       
        // 4. ViewResolver를 통해 viewName에 해당하는 화면을 검색한다.
        String view = null;
        if (!viewName.contains(".do")) {
            view = viewResolver.getView(viewName);
        } else {
            view = viewName;
        }
       
        // 5. 검색된 화면으로 이동한다.
        response.sendRedirect(view);
    } 
}

 


작성된 컨트롤러 클래스들의 위치

Q. Controller를 이렇게 복잡하게 구현하는 이유는 무엇일까?

A. 프레임워크에서 DispatcherServlet을 제공한다. 따라서 새로운 기능이 추가되더라도 DispatcherServlet 클래스 소스는 변경할 필요가 없도록 개발해야 한다.