Spring | 스프링 퀵 스타트 - MVC 프레임워크 개발 1/n

파과·2022년 8월 15일

스프링 퀵 스타트 285쪽


우리가 개발할 MVC 프레임워크 구조

DispatcherServlet : 유일한 서블릿 클래스로서 모든 클라이언트의 요청을 가장 먼저 처리하는 Front Controller
HandlerMapping : 클라이언트의 요청을 처리할 Controller 매핑
Controller : 실질적인 클라이언트의 요청 처리
ViewResolver : Controller가 리턴한 View 이름으로 실행될 JSP 경로 완성

Controller 인터페이스

Controller를 구성하는 요소 중에서 DispatcherServlet은 클라이언트의 요청을 가장 먼저 받아들이는 Front Controller다. 하지만 클라이언트의 요청을 처리하기 위해 DispatcherServlet이 하는 일은 거의 없으며, 실질적인 요청 처리는 각 Controller에서 담당한다.

구체적인 Controller 클래스들을 구현하기에 앞서 모든 Controller를 같은 타입으로 관리하기 위한 인터페이스를 만들어야 한다. 클라이언트의 요청을 받은 DispatcherServlet은 HandlerMapping을 통해 Controller 객체를 검색하고, 검색된 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);
}

LoginController 클래스

Controller 인터페이스를 구현한 LoginController 클래스.
로그인 처리 소스는 DispatcherServlet의 로그인 처리 기능과 같다. 다만 Controller 인터페이스의 handleRequest() 메소드를 재정의했으므로 로그인 처리 기능의 마지막은 이동할 화면을 리다이렉트하지 않고 리턴하는 것으로 처리한다.

로그인에 실패했을 때 이동할 화면 정보가 login.jsp가 아니라 그냥 login이다. 이는 나중에 추가할 ViewResolver 클래스를 만들어야 이해할 수 있다.

package com.springbook.view.user;

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

import com.springbook.biz.user.UserVO;
import com.springbook.biz.user.impl.UserDAO;
import com.springbook.view.controller.Controller;

public class LoginController implements Controller {

	@Override
	public String handleRequest(HttpServletRequest request, HttpServletResponse response) {
		// TODO Auto-generated method stub
		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";
		}
	}

}

HandlerMapping 클래스

HandlerMapping은 모든 Controller 객체들을 저장하고 있다가, 클라이언트의 요청이 들어오면 요청을 처리할 특정 Controller를 검색하는 기능을 제공한다. HandlerMapping 객체는 DispatcherServlet이 사용하는 객체이다. 따라서 DispatcherServlet이 생성되고 init() 메소드가 호출될 때 단 한 번 생성된다.

HandlerMapping은 Map 타입의 컬렉션을 멤버변수로 가지고 있으면서 게시판 프로그램에 필요한 모든 Controller 객체들을 등록하고 관리한다.

getController() 메소드는 매개변수로 받은 path에 해당하는 Controller 객체를 HashMap 컬렉션으로부터 검색하여 리턴한다. 지금은 LoginController 객체 하나만 등록되어 있지만, 앞으로 계속 Controller 객체들이 추가될 것이다. 따라서 HashMap에 등록된 정보를 보면 Controller 객체가 어떤 ".do" 요청과 매핑되어 있는지 확인할 수 있다.

package com.springbook.view.controller;

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

import com.springbook.view.user.LoginController;

public class HandlerMapping {
	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);
	}
}

ViewResolver 클래스

ViewResolver 클래스는 Controller가 리턴한 View 이름에 접두사(prepix)와 접미사(suffix)를 결합하여 최종으로 실행될 View 경로와 파일명을 완성한다. ViewResolver도 HandlerMapping과 마찬가지로 DispatcherServlet의 init()메소드가 호출될 때 생성된다.

ViewResolver는 setPrefix()와 setSuffix() 메소드로 접두사와 접미사를 초기화한다. 그리고 getView() 메소드가 호출될 때 viewName 앞뒤로 prefix와 suffix를 결합해 리턴한다. `

package com.springbook.view.controller;

import lombok.Setter;


public class ViewResolver {
	@Setter
	public String prefix;
	
	@Setter
	public String suffix;
	
	public String getView(String viewName) {
		return prefix + viewName + suffix;
	}
}

DispatcherServlet 클래스

DispatcherServlet은 Front Controller 기능의 클래스로서 Controller 구성 요소 중 가장 중요한 역할을 수행한다.

서블릿의 init()메소드는 서블릿 객체가 생성된 후에 멤버변수를 초기화하기 위해 자동으로 실행된다. 따라서 init()메소드에서 DispatcherServlet이 사용할 HandlerMapping과 ViewResolver객체를 초기화한다. 그리고 DispatcherServlet은 이렇게 생성된 HandlerMapping과 ViewResolver를 이용하여 사용자의 요청을 처리한다.

process()에서는 클라이언트의 요청 path에 해당하는 Controlelr를 검색하기 위해 HandlerMapping 객체의 getController()메소드를 호출한다. 그리고 검색된 Controller의 handleRequest() 메소드를 호출해 요청에 해당하는 로직을 처리하고 나면 이동할 화면 정보가 리턴된다. 마지막으로 Controller가 리턴한 View이름을 이용하여 실행될 View를 찾아 해당 화면으로 이동한다.

package com.springbook.view.controller;

import java.io.IOException;

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

public class DispatcherServlet {
	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{
		process(request, response);
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
		request.setCharacterEncoding("UTF-8");
		process(request, response);
	}
	
	private void process(HttpServletRequest request, HttpServletResponse response) throws IOException {
		//1. 클라이언트의 요청 path 정보를 추출한다.
		String uri = request.getRequestURI();
		String path = uri.substring(uri.lastIndexOf("/"));
		
		//2. HandlerMapping을 통해 path에 해당하는 Controller를 검색한다.
		Controller ctrl = handlerMapping.getController(path);
		
		//3. 검색된 Controller를 실행한다.
		String viewName = ctrl.handleRequest(request, response);
		
		//4. ViewResolver를 통해 viewName에 해당하는 화면을 검색한다.
		String view = null;
		if(!viewName.contains(".do")) {
			view = viewResolver.getView(viewName);
		}else {
			view = viewName;
		}
		
		//5. 검색된 화면으로 이동한다.
		response.sendRedirect(view);
	}
}

로그인 기능의 동작 과정

  1. 클라이언트가 로그인 버튼을 클릭하여 "/login.do" 요청을 전송하면 DispatcherServlet이 요청을 받는다.
  2. DispatcherServlet은 HandlerMapping 객체를 통해 로그인 요청을 처리할 LoginController를 검색한다.
  3. 검색된 LoginController의 handleRequest() 메소드를 호출하면 로그인 로직이 처리된다.
  4. 로그인 처리 후에 이동할 화면 정보가 리턴되면
  5. DispatcherServlet은 ViewResolver를 통해 접두사와 접미사가 붙은 JSP파일의 이름과 경로를 리턴받는다.
  6. 그리고 최종적으로 JSP를 실행하고 실행 결과가 브라우저에 응답한다.

0개의 댓글