본문 바로가기
교재 실습/자바 웹 개발 워크북

76. 프런트 컨트롤러의 도입 (2)

by Jint 2022. 7. 24.

3. 프런트 컨트롤러 만들기

프런트 컨트롤러를 만든다. web06 프로젝트의 spms.servlets 패키지에 DispatcherServlet 클래스를 생성한다. 물론 HttpServlet 클래스를 상속받아야 한다.

package spms.servlets;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import spms.vo.Member;

@SuppressWarnings("serial")
@WebServlet("*.do") //프런트 컨트롤러의 배치
public class DispatcherServlet extends HttpServlet {//서블릿이기 때문에 HttpServlet 상속
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html; charset=UTF-8");
		String servletPath = request.getServletPath(); //서블릿 경로 추출
		try {
			String pageControllerPath = null;
			
			//페이지 컨트롤러로 위임
			if("/member/list.do".equals(servletPath)) {
				pageControllerPath = "/member/list";
			}else if("/member/add.do".equals(servletPath)) {
				pageControllerPath = "/member/add";
				if(request.getParameter("email") != null) {
					//요청 매개변수로부터 VO 객체 준비
					request.setAttribute("member", new Member().setEmail(request.getParameter("email"))
															   .setPassword(request.getParameter("password"))
															   .setName(request.getParameter("name")));
				}
			}else if("/member/update.do".equals(servletPath)) {
				pageControllerPath = "/member/update";
				if(request.getParameter("email") != null) {
					//요청 매개변수로부터 VO 객체 준비
					request.setAttribute("member", new Member().setNo(Integer.parseInt(request.getParameter("no")))
															   .setEmail(request.getParameter("email"))
															   .setName(request.getParameter("name")));
				}
			}else if("/member/delete.do".equals(servletPath)) {
				pageControllerPath = "/member/delete";
			}else if("/auth/login.do".equals(servletPath)) {
				pageControllerPath = "/auth/login";
			}else if("/auth/logout.do".equals(servletPath)) {
				pageControllerPath = "/auth/logout";
			}
			//페이지 컨트롤러로 실행 위임
			RequestDispatcher rd = request.getRequestDispatcher(pageControllerPath);
			rd.include(request, response);

			//JSP로 위임
			String viewUrl = (String)request.getAttribute("viewUrl");
			if(viewUrl.startsWith("redirect:")) {
				response.sendRedirect(viewUrl.substring(9));
				return;
			}else {
				rd = request.getRequestDispatcher(viewUrl);
				rd.include(request, response);
			}
		} catch (Exception e) {//오류 처리
			e.printStackTrace();
			request.setAttribute("error", e);
			RequestDispatcher rd = request.getRequestDispatcher("/Error.jsp");
			rd.forward(request, response);
		}
	}
}

프런트 컨트롤러도 서블릿이기 때문에 HttpServlet 클래스를 상속받는다. 그런데 여기서 주목할 점은 오버라이딩하는 메서드이다.

@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    ...
}

doGet() 메서드, doPost() 메서드가 아니라 service() 메서드이다. 이 메서들의 매개변수를 보면 ServletRequest와 ServletResponse가 아니라 HttpServletRequest와 HttpServletResponse 이다. 즉 Servlet 인터페이스에 선언된 메서드가 아니다.

그럼 이 메서드는 서블릿 컨테이너(톰캣)가 직접 호출하지 않는다는 것인데, 언제 호출되는지 다음 그림을 통해 살펴본다(그림 1).

그림 1 (HttpServlet에 추가된 service() 메서드)

① 클라이언트로부터 요청이 들어오면 서블릿 컨테이너는 규칙에 따라 Servlet 인터페이스에 선언된 service() 메서드를 호출한다(원래는 스레드가 호출함).

② 이 service(ServletRequest, ServletResponse) 메서드는 HttpServlet 클래스에 추가된 동일한 이름의 service(HttpServletRequest, HttpServletResponse) 메서드를 호출한다. 이름은 같지만, 매개변수가 다르다.

③ service(HttpServletRequest, HttpServletResponse) 메서드는 HTTP 요청 프로토콜을 분석하여 다시 doGet() 메서드, doPost() 메서드 등을 호출한다.

 

GET 요청이든, POST 요청이든 service(HttpServletRequest, HttpServletResponse) 메서드가 호출된다는 것이다. 그래서 이 메서드를 오버라이딩 한 것이다.

물론 "doGet() 메서드나 doPost() 메서드를 오버라이딩해도 될텐데"라고 생각할 수 있다. doGet() 메서드, doPost() 메서드를 오버라이딩하여 프런트 컨트롤러를 만들 수 있지만, 그럼에도 service() 메서드를 오버라이딩 한 이유는 첫째, GET, POST 뿐만 아니라 다양한 요청 방식에도 대응하기 위해서다. 둘째, 가능한 HttpServlet 클래스의 내부 구조를 조금이라도 파악하기 위함이다.

 

- JSP로 위임

페이지 컨트롤러의 실행이 끝나면, 화면 출력을 위해 ServletRequest에 보관된 뷰 URL로 실행을 위임한다. 단 뷰 URL이 "redirect:"로 시작한다면, 인클루딩 하는 대신 sendRedirect() 메서드를 호출한다.

//JSP로 위임
String viewUrl = (String)request.getAttribute("viewUrl");
if(viewUrl.startsWith("redirect:")) {
    response.sendRedirect(viewUrl.substring(9));
    return;
}else {
    rd = request.getRequestDispatcher(viewUrl);
    rd.include(request, response);
}

 

- 오류 처리

서블릿을 만들 때 매번 작성한 것 중의 하나가 오류 처리이다. 이제 프런트 컨트롤러에서 오류 처리를 담당하기 때문에, 페이지 컨트롤러를 작성할 때는 오류 처리 코드를 넣을 필요가 없다.

try {
    ...
} catch (Exception e) {//오류 처리
    e.printStackTrace();
    request.setAttribute("error", e);
    RequestDispatcher rd = request.getRequestDispatcher("/Error.jsp");
    rd.forward(request, response);
}

 

- 프런트 컨트롤러의 배치

@WebServlet 어노테이션을 사용하여 프런트 컨트롤러의 배치 URL을 "*.do"로 지정한다. 즉 클라이언트의 요청 중에서 서블릿 경로 이름이 .do로 끝나는 경우는 DispatcherServlet 클래스가 처리하겠다는 의미이다.

@WebServlet("*.do") //프런트 컨트롤러의 배치
public class DispatcherServlet extends HttpServlet {//서블릿이기 때문에 HttpServlet 상속
    ...
}

 

참고도서 : https://freelec.co.kr/book/1674/

 

[열혈강의] 자바 웹 개발 워크북

[열혈강의] 자바 웹 개발 워크북

freelec.co.kr

댓글