2 분 소요

6.4 MVC 프레임워크 요구사항 1단계

JSP에 상당 부분의 로직을 포함하는 것이 초기 개발 속도는 빨랐지만 유지보수 비용은 증가했다.

이 같은 단점을 보완해 유지보수 비용을 줄이기 위해 MVC(Model, View, Controller) 패턴 기반으로 웹 애플리케이션을 개발하는 방향으로 발전했다.

6.4.1 요구사항

MVC 패턴을 지원하는 프레임워크를 구현하라

모든 요청을 RequestHandler 가 받아서 요청 URL 에 따라 분기처리 했듯이

서블릿도 모든 요청을 하나의 서블릿이 받은 후 요청 URL 에 따라 분기 처리하는 방식으로 구현한다.

컨트롤러 : 사용자의 최초 진입 지점

뷰에 직접 접근하는 것을 막고 항상 컨트롤러를 통해 접근하도록 해야 한다.

ex) /user/form.jsp 과 같이 jsp 로 직접 접근하지 않도록 한다.

모든 클라이언트 요청은 먼저 DispatcherServlet 이 받는다.

DispatcherServlet 은 요청 URL 에 따라 해당 컨트롤러에 작업을 위임한다.

@WebServlet 으로 URL 을 매핑할 때 urlPatterns=”/” 와 같이 설정하면 된다.

css, javascript, image 처럼 정적인 자원은 컨트롤러가 필요없고 해당 요청은 DispatcherServlet 처리에서 제외하기 위해 서블릿 필터를 추가한다. (본 프로젝트에는 이미 ResourceFilter 가 추가되어 있음)

6.4.2 요구사항 분리 및 힌트

  • 모든 요청을 서블릿 하나가 받을 수 있도록 url을 매핑한다.

      @WebServlet(name = "dispatcher", urlPatterns = "/", loadOnStartup = 1)
      // loadOnStartup 속성이 무슨 일을 하는 지 학습해 본다.
      // 서블릿은 "/" 로 설정함으로써 모든 요청을 하나의 서블릿으로 매핑할 수 있다.
    
  • Controller 인터페이스를 추가한다.

      public interface Controller {
      	String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
      }
      // execute() 메소드의 반환 값이 String 인 것을 눈여겨보자.
    
  • 서블릿으로 구현되어 있는 회원관리 기능을 앞 단계에서 추가한 Controller 인터페이스 기반으로 다시 구현한다. execute() 메소드의 반환 값은 리다이렉트 방식으로 이동할 경우 redirect:로 시작하고 포워드 방식으로 이동할 경우 jsp 경로를 반환한다.

      public class ListUserController implements Controller {
      	@Override
      	public String execute(HttpServletRequest request, 
      		HttpServletResponse response) throws Exception {
        		
      		if(!UserSessionUtils.isLogined(req.getSession())) {
      			return "redirect:/users/loginForm";		
      		}
      		req.setAttribute("users", Database.findAll());
      		return "/user/list.jsp";
      	}
      }
    
  • RequestMapping 클래스를 추가해 요청 url 과 컨트롤러 매핑을 설정한다.
  • 특별한 로직 없이 뷰에 대한 이동만을 담당하는 ForwardController 를 추가한다. (회원가입 화면, 로그인 화면)
  • DispatcherServlet 에서 요청 URL 에 해당하는 컨트롤러를 찾아 execute() 메소드를 호출해 실질적인 작업을 위임한다.
  • 컨트롤러의 execute() 메소드 반환 값 string 을 받아 서블릿에서 JSP 로 이동할 때의 중복을 제거한다.

6.5 MVC 프레임워크 구현 1단계

구현 완료 참고 branch : step2-user-with-mvc-framework

클라이언트 요청에 대한 처리를 담당하는 부분을 Controller interface로 추상화한다.

public interface Controller {
	String execute(HttpServletRequest req, HttpServletResponse resp) throws Exception;
}

지금까지 HttpServlet 을 상속해 구현한 서블릿 코드를 Controller 인터페이스를 구현하도록 변경한다.

예)

as-is

@WebServlet("/users")
public class ListUserController extends HttpServelt {
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		if(!UserSessionUtils.isLogined(req.getSession())) {
			resp.sendRedirect("/users/loginForm");
			return;
		}

		req.setAttribute("users", Database.findAll());

		RequestDispatcher rd = req.getRequestDispatcher("/user/list.jsp");
		rd.forward(req, resp);ㅁㄴㅇ
	}
}

to-be

public class ListUserController implements Controller {
	@Override
	public String execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		if(!UserSessionUtils.isLogined(req.getSession())) {
			return "redirect:/users/loginForm";
		}

		req.setAttribute("users", Database.findAll());
		return "/user/list.jsp";
	}
}

특별한 로직 없이 뷰(JSP) 에 대한 이동만을 담당하는 ForwardController 를 추가한다.

public class ForwardController implements Controller {

	private String forwardUrl;

	public ForwardController (String forwardUrl) {
		this.forwardUrl = forwardUrl;
		if(forwardUrl == null) {
			throw new NullPointerException("forwardUrl is null.");
		}
	}

	@Override
	public String execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		return forwardUrl;
	}
}