MVC Frame Work를 직접 구현해보기
이전 포스팅에서는 MVC1과 MVC2 모델의 정의와 그 예시를 간단하게 보았습니다. 구현을 하면서 DispatcherServlet 클래스 하나로 Controller를 상속받아 기능을 구현했습니다.
이렇게 하나의 서블릿 클래스로 Controller를 구현하면 클라이언트의 모든 요청을 하나의 서블릿이 처리하게 됩니다. 따라서 기능이 점점 늘어나면 수많은 if 로직이 생길 수 밖에 없고 이는 오히려 개발과 유지보수 측면에서 더 어렵게 만듭니다.
따라서 Controller를 구현 할 때 다양한 디자인 패턴을 결합하여 개발과 유지보수의 편의성이 보장되도록 잘 만들어야 합니다. 그래서 등장한 것이 Spring MVC 프레임 워크입니다.
오늘의 포스팅은 Spring MVC를 공부하기 전에 동일한 구조의 Spring MVC 구조를 직접 구현 해보고자 합니다.
* MVC 프레임워크에 사용된 클래스의 기능
클래스 | 기능 |
DispatcherServlet | 유일한 서블릿 클래스로 모든 클라이언트의 요청을 가장 먼저 처리하는 Front Controller |
HandlerMapping | 클라이언트 요청을 처리할 Controller 매핑 |
Controller | 실질적인 클라이언트의 요청을 처리 |
ViewResolver | Controller가 리턴한 View 이름으로 실행될 JSP 경로를 완성 |
※ 프레임 워크 구현
- Controller interface
Controller를 구성하는 요소 중 DispatcherServlet은 클라이언트의 요청을 가장 먼저 받는 FrontController로 실제적으로 작업은 interface를 상속받은 각 Controller에서 처리합니다. 이렇게 하면서 다른 타입을 가지는 Controller를 같은 타입으로 관리할 수 있게 됩니다.
package tommy.spring.web.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface Controller {
String handleRequest(HttpServletRequest request, HttpServletResponse response);
}
- LoginController
package tommy.spring.web.user;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import tommy.spring.web.controller.Controller;
import tommy.spring.web.user.impl.UserDAO;
// 컨트롤러 인터페이스 상속
public class LoginController implements Controller {
@Override
public String handleRequest(HttpServletRequest request, HttpServletResponse response) {
System.out.println("로그인 처리");
// 1. 사용자 입력 정보 추출
String id = request.getParameter("id");
String password = request.getParameter("password");
// 2. 데이터베이스 연동 처리
UserVO vo = new UserVO();
vo.setId(id);
vo.setPassword(password);
UserDAO userDAO = new UserDAO();
UserVO user = userDAO.getUser(vo);
// 3. 화면 네비게이션
// ViewResolver 클래스를 위해 확장자 없이 반환
if (user != null) {
return "getBoardList.do";
} else {
return "login";
}
}
}
- HandlerMapping
모든 Controller 객체를 저장하고 있다가 클라이언트의 요청이 들어오면 요청을 처리할 특정 Controller를 검색하는 기능을 제공합니다.
또한 HandlerMapping은 DispatcherServlet이 생성되고 init() 메서드가 호출될 때 한 번 생성됩니다.
package tommy.spring.web.controller;
import java.util.HashMap;
import java.util.Map;
import tommy.spring.web.user.LoginController;
public class HandlerMapping {
private Map<String, Controller> mappings;
public HandlerMapping() {
mappings = new HashMap<String, Controller>();
mappings.put("/login.do", new LoginController());
// 나중에 이 부분에 명령어(path)와 Controller 객체가 추가됨.
}
public Controller getController(String path) {
return mappings.get(path);
}
}
- ViewResolever
ViewResolver는 Controller가 리턴한 View 이름에 접두사(prefix)와 접미사(suffix)를 결합하여 최종적으로 실행될 View 경로와 파일명을 완성해줍니다.
또한 ViewResolver는 DIspatcherServlet이 생성되고 init() 메서드가 호출될 때 생성됩니다.
package tommy.spring.web.controller;
public class ViewResolver {
private String prefix;
private 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;
}
}
- DispatcherServlet
package tommy.spring.web.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "action", urlPatterns = { "*.do" })
public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private HandlerMapping handlerMapping;
private ViewResolver viewResolver;
public void init() throws ServletException {
handlerMapping = new HandlerMapping();
viewResolver = new ViewResolver();
viewResolver.setPrefix("./");
viewResolver.setSuffix(".jsp");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
processRequest(request, response);
}
private void processRequest(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// 1. 클라이언트 정보를 추출한다.
String uri = request.getRequestURI();
String path = uri.substring(uri.lastIndexOf("/"));
System.out.println(path);
// 2. HandlerMapping을 통해 path에 해당하는 Controller를 검색한다.
Controller controller = handlerMapping.getController(path);
// 3. 검색된 Controller를 실행한다.
String viewName = controller.handleRequest(request, response);
// 4. ViewResolver를 통해 viewName애 해당하는 화면을 검색한다.
String view = null;
if (!viewName.contains(".do")) {
view = viewResolver.getView(viewName);
} else {
view = viewName;
}
// 5. 검색된 화면으로 이동한다.
response.sendRedirect(view);
}
}
* 실행 및 결과 확인
http://localhost:8080/myboard/login.do 입력하여 확인한다.
※ 로그인 기능 동작 과정 정리
1) 클라이언트가 로그인 버튼을 클릭해서 "/login.do" 요청을 전송하면 DispatcherServlet 요청을 받는다.
2) DispatcherServlet은 HandlerMapping 객체를 통해 로그인 요청을 처리할 LoginController를 검색 합니다.
3) 검색된 LoginController의 handleRequest() 메서드를 호출 하면 로그인 로직이 처리됩니다.
4) 로그인 처리 후에 이동할 화면 정보가 리턴된 후
5) DispatcherServlet은 ViewResolver를 통해 접두사와 접미사가 붙은 JSP 파일의 이름과 경로를 리턴 받습니다.
6) 최종적으로 JSP를 실행하고 실행 결과가 브라우저에 응답됩니다.
※ 정리
Spring MVC의 구조 형태를 사용하는 이유는 Controller에서 가장 중요한 DispatcherServlet은 유지보수 과정이나 새로운 기능을 추가해도 절대 수정되지 않기 때문입니다.
만약 회원가입 기능을 추가한다고 할 때 InsertUserController 클래스를 새로 만들고 HandlerMapping에 InsertUserController 클래스를 등록만 해주면 됩니다. 이러한 과정에서 DispatcherServlet은 어떤 수정 작업도 하지 않습니다.
'Frame Work > Spring' 카테고리의 다른 글
Spring Annotation (2) | 2022.11.01 |
---|---|
Spring MVC (0) | 2022.10.27 |
MVC pattern (0) | 2022.10.25 |
Spring Transaction (0) | 2022.10.22 |
Spring JDBC (0) | 2022.10.21 |