MVC → Model, View, Controller
스프링 MVC는 Servlet API 기반의 웹 프레임워크이다.
프론트 컨트롤러 패턴을 중심으로 설계되었다.
📌 MVC Pattern의 핵심은 Model ↔ View, View ↔ Controller 분리이다.
페이지 컨트롤러는 웹 사이트의 특정 페이지 또는 작업에 대한 요청을 처리하는 개체이다.
@WebServlet
은 Spring 어노테이션 이전에 웹 요청을 처리하는 방식인 듯. HttpServlet을 상속받아서 사용함.HttpServlet
: 요청 처리의 기본 로직을 제공하는 추상 클래스@WebServlet("/hello")
: 이 클래스를 /hello
요청에 매핑@WebServlet(
name = "StudentServlet",
urlPatterns = "/student-record")
public class StudentServlet extends HttpServlet {
private StudentService studentService = new StudentService();
private void processRequest(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String studentID = request.getParameter("id");
if (studentID != null) {
int id = Integer.parseInt(studentID);
studentService.getStudent(id)
.ifPresent(s -> request.setAttribute("studentRecord", s));
}
RequestDispatcher dispatcher = request.getRequestDispatcher(
"/WEB-INF/jsp/student-record.jsp");
dispatcher.forward(request, response);
}
@Override
protected void doGet(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
}
프론트 컨트롤러는 모든 요청을 단일 처리기 객체로 집중하는 방법으로 자주 수행하는 공통적인 작업을 한대 모아 처리할 수 있는 장점이 있다.
아래 코드는 FrontController 패턴의 예시인데,
다음과 같이 FrontController + Strategy 패턴으로 작성됨.
이를 Spring MVC에 대입해보면 FrontController는 Dispatcher Servlet이 되는 거고 Front Command 등은 AdaptorHandler, PageController 등이 되는 것.
public class FrontControllerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
FrontCommand command = getCommand(request);
command.init(getServletContext(), request, response);
command.process();
}
private FrontCommand getCommand(HttpServletRequest request) {
try {
Class type = Class.forName(String.format(
"com.baeldung.enterprise.patterns.front."
+ "controller.commands.%sCommand",
request.getParameter("command")));
return (FrontCommand) type
.asSubclass(FrontCommand.class)
.newInstance();
} catch (Exception e) {
return new UnknownCommand();
}
}
}
public abstract class FrontCommand {
protected ServletContext context;
protected HttpServletRequest request;
protected HttpServletResponse response;
public void init(
ServletContext servletContext,
HttpServletRequest servletRequest,
HttpServletResponse servletResponse) {
this.context = servletContext;
this.request = servletRequest;
this.response = servletResponse;
}
public abstract void process() throws ServletException, IOException;
protected void forward(String target) throws ServletException, IOException {
target = String.format("/WEB-INF/jsp/%s.jsp", target);
RequestDispatcher dispatcher = context.getRequestDispatcher(target);
dispatcher.forward(request, response);
}
}
public class SearchCommand extends FrontCommand {
@Override
public void process() throws ServletException, IOException {
Book book = new BookshelfImpl().getInstance()
.findByTitle(request.getParameter("title"));
if (book != null) {
request.setAttribute("book", book);
forward("book-found");
} else {
forward("book-notfound");
}
}
}
이해한 내용 바탕으로 지피티에게 요약 부탁해봄.
전통 서블릿 구조 | Spring MVC 구조 |
---|---|
FrontControllerServlet | DispatcherServlet |
FrontCommand (추상 명령) | HandlerAdapter + Controller (또는 @RequestMapping 메서드) |
SearchCommand , DeleteCommand 등 | @Controller 안의 메서드들 |
forward("xxx.jsp") | ViewResolver 가 JSP 혹은 템플릿으로 포워딩 |
평소 어렵게 느꼈던 개념들이 모두 나왔다. 이에 대해 하나씩 정리하다보면 Spring MVC의 Front Controller를 개략적으로 이해 가능할 것 같다.
요청 URL에 해당하는 핸들러(Controller)를 찾아준다.
찾아낸 핸들러를 적절한 방식으로 실행해주는 어댑터.
HandlerMapping으로 어떤 Handler가 실행될 지 알았다면, 이를 HandlerAdapter에 위임하여 실행시킨다.
📌 HandlerMapping이 Handler를 찾아주는데, 왜 HandlerMapping에서 바로 실행하지 않고 HandlerAdaptor의 변환을 기다릴까?
→ 확장성 때문이다!
// 옛날 방식
public class MyController implements Controller {
public ModelAndView handleRequest(...) { ... }
}
// 요즘 방식 (애노테이션 기반)
@Controller
public class BookController {
@GetMapping("/search")
public String search(...) { ... }
}
다음과 같이 여러 타입의 Handler가 존재할 수 있는데, 이런 여러 타입의 Handler를 모두 지원하기 위해서 중간 객체인 HandlerAdaptor를 두는 것.
왜냐하면 Handler는 타입마다 호출 방식이 모두 제각각이기 때문이다.
Handler Type | 호출 방식 |
---|---|
Controller 인터페이스 | .handleRequest(req, resp) |
@RequestMapping | 리플렉션으로 메서드 찾아 호출 |
HttpRequestHandler | .handleRequest() |
사용자 정의 핸들러 | 사용자 방식대로 호출해야 함 |
Controller가 반환한 뷰 이름(String)을 실제 뷰 경로(JSP, Thymeleaf 등)로 변환해준다.
Dispatcher Servlet의 내용이 방대한 이유는 말 그대로 FrontController이기 때문이다.
각각의 개념이 무엇인지 얕게 이해했으므로 실제로 사용하면서 추가로 정리해보자.