[JavaWeb] 6장 서블릿 JSP를 활용해 동적인 웹 애플리케이션 개발하기 - 4
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;
}
}
모든 서블릿을 Controller 인터페이스를 구현하도록 변경하는 작업을 완료하면
요청 URL 과 컨트롤러 매핑을 담당하는 작업을 RequestMapping 에 추가한다.
public class RequestMapping {
private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);
private Map<String, Controller> mappings = new HashMap<>();
void initMapping() {
mappings.put("/", new HomeController());
mappings.put("/users/form", new ForwardController("/user/form.jsp"));
mappings.put("/users/loginForm", new ForwardController("/user/login.jsp"));
mappings.put("/users", new ListUserController());
mappings.put("/users/login", new LoginController());
mappings.put("/users/profile", new ProfileController());
mappings.put("/users/logout", new LogoutController());
mappings.put("/users/create", new CreateUserController());
mappings.put("/users/updateForm", new UpdateFormUserController());
mappings.put("/users/update", new UpdateUserController());
logger.info("Initialized Request Mapping.");
}
public Controller findController(String url){
return mappings.get(url);
}
void put(String url, Controller controller) {
mappings.put(url, controller);
}
}
RequestMapping 은 서비스에서 발생하는 모든 요청 URL 과 각 URL 에 대한 서비스를 담당할 컨트롤러를 연결하는 작업을 한다.
클라이언트의 모든 요청을 받아 URL 에 해당하는 컨트롤러로 작업을 위임하고, 실행된 결과 페이지로 이동하는 작업을 담당할 DispatcherServlet 을 구현한다.
@WebServlet(name = "dispatcher", urlPatterns = "/", loadOnStartup = 1)
public class DispatcherServlet extends HttpServelt {
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);
private static final String DEFAULT_REDIRECT_PREFIX = "redirect:";
private RequestMapping rm;
@Override
public void init() throws ServletException {
rm = new RequestMapping();
rm.initMapping();
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String requestUri = req.getRequestURI();
logger.debug("Method : {}, Request URI : {}", req.getMethod(), requestUri);
Controller controller = rm.findController(requestUri);
try {
String viewName = controller.execute(req, resp);
move(viewName, req, resp);
} catch (Throwable e) {
logger.error("Exception : {}", e);
throw new ServletException(e.getMessage());
}
}
private void move(String viewName, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(viewName.startsWith(DEFAULT_REDIRECT_PREFIX)){
resp.sendRedirect(viewName.substring(DEFAULT_REDIRECT_PREFIX.length()));
return;
}
RequestDispatcher rd = req.getRequestDispatcher(viewName);
rd.forward(req, resp);
}
}
정적 자원 처리는 DispatcherServlet 으로 요청되기 이전에
core.web.filter.ResourceFilter 에서 “default” 서블릿이 처리하도록 구현하고 있다.
HomeController 에서 “/”로 매핑하여 접근 시, webapp 디렉토리에 index.jsp 가 존재한다면 HomeController 가 아닌 index.jsp 로 요청이 처리된다는 점을 주의해야한다. 이렇게 처리되는 이유는 path 가 없는 경우 (index.jsp 가?) 처리를 담당하는 기본 파일로 설정되어 있기 때문이다.
따라서 HomeController 에서 이동할 뷰를 지정할 때 index.jsp 가 아니라 home.jsp 로 사용했다.
서블릿을 매핑할 때 사용하는 loadOnStartup 설정은 서블릿의 인스턴스를 생성하는 시점과 초기화를 담당하는 init() 메소드를 어느 시점에 호출할 것인가를 결정하는 설정이다.
loadOnStartup 을 설정하지 않으면 서블릿 컨테이너가 시작을 완료한 후 클라이언트의 요청이 최초로 발생하는 시점에 서블릿 인스턴스 생성과 초기화가 이루어진다.
loadOnStartup 을 설정하면 서블릿 컨테이너가 시작하는 시점에 서블릿 인스턴스 생성과 초기화가 이루어진다.
loadOnStartup 숫자 값은 여러 개의 서블릿의 초기화 순서를 결정하며 숫자가 낮은 순으로 우선순위를 갖는다.