인프런 김영한 님의 강의를 참고했습니다.
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의
웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -
www.inflearn.com
저번에 만들었던 서블릿에 컨트롤러를 추가해보자.
현재 만들어둔 컨트롤러는 공통된 부분이 많고 하는 일도 비슷하다.
그럴 때는 FrontController를 생성해준다.
이 FrontController가 요청을 받고 그 요청에 맞는 컨트롤러를 호출해주는 역할을 한다.
우선 프론트 컨트롤러 자체를 도입해보자.
우선 다형성을 이용해 컨트롤러 인터페이스를 만들어보자.
package hello.servlet.web.frontcontroller.version1;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface ControllerV1 {
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
이렇게 만들고 각 컨트롤러들이 이 인터페이스를 구현하게 해주면 요청에 따른 컨트롤러는 모두 준비가 된다.
이제 FrontController를 만들어준다.
package hello.servlet.web.frontcontroller.version1;
import hello.servlet.web.frontcontroller.version1.controller.StudentFormControllerV1;
import hello.servlet.web.frontcontroller.version1.controller.StudentListControllerV1;
import hello.servlet.web.frontcontroller.version1.controller.StudentSaveControllerV1;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
private Map<String, ControllerV1> controllerMap = new HashMap<>();
public FrontControllerServletV1(){
controllerMap.put("/front-controller/v1/students/new-form", new StudentFormControllerV1());
controllerMap.put("/front-controller/v1/students/save", new StudentSaveControllerV1());
controllerMap.put("/front-controller/v1/students", new StudentListControllerV1());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ControllerV1 controller = controllerMap.get(req.getRequestURI());
if(controller == null){
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
controller.process(req, resp);
}
}
이렇게 Map에 컨트롤러들을 담아준 후 URI를 확인하여 거기에 맞는 컨트롤러로 보내준다.
이제 계속 리팩토링을 진행해보자.
모든 컨트롤러에서 중복되는 부분이 있다.
이 부분을 모아주어야 깔끔하고, 후에 수정하기도 쉽다.

이런 상태에서 중간에 MyView를 추가하여 dispatcher.forward()를 맡길 예정이다.
package hello.servlet.web.frontcontroller;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyView {
private String viewPath;
public MyView(String viewPath){
this.viewPath = viewPath;
}
public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewPath);
requestDispatcher.forward(request, response);
}
}
이렇게 생성자로 viewPath를 입력받아서 render를 이용하여 jsp로 넘겨준다.
나머지 코드들은 저 부분을 빼서 다시 작성한다고 생각하면 된다.
package hello.servlet.web.frontcontroller.version2;
import hello.servlet.web.frontcontroller.MyView;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface ControllerV2 {
MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
package hello.servlet.web.frontcontroller.version2.controller;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.version2.ControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class StudentFormControllerV2 implements ControllerV2 {
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
return new MyView("/WEB-INF/views/new-form.jsp");
}
}
package hello.servlet.web.frontcontroller.version2.controller;
import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.version2.ControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class StudentSaveControllerV2 implements ControllerV2 {
private StudentRepository studentRepository = StudentRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String studentName = request.getParameter("studentName");
int year = Integer.parseInt(request.getParameter("year"));
Student student = new Student(studentName, year);
studentRepository.save(student);
request.setAttribute("student", student);
return new MyView("/WEB-INF/views/save-result.jsp");
}
}
package hello.servlet.web.frontcontroller.version2.controller;
import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.version2.ControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
public class StudentListControllerV2 implements ControllerV2 {
private StudentRepository studentRepository = StudentRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Student> students = studentRepository.findAll();
request.setAttribute("students", students);
return new MyView("/WEB-INF/views/students.jsp");
}
}
그러고 FrontController는 이렇게 작성을 해준다.
package hello.servlet.web.frontcontroller.version2;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.version2.controller.StudentFormControllerV2;
import hello.servlet.web.frontcontroller.version2.controller.StudentListControllerV2;
import hello.servlet.web.frontcontroller.version2.controller.StudentSaveControllerV2;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {
private Map<String, ControllerV2> controllerMap = new HashMap<>();
public FrontControllerServletV2(){
controllerMap.put("/front-controller/v1/students/new-form", new StudentFormControllerV2());
controllerMap.put("/front-controller/v1/students/save", new StudentSaveControllerV2());
controllerMap.put("/front-controller/v1/students", new StudentListControllerV2());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ControllerV2 controller = controllerMap.get(req.getRequestURI());
if(controller == null){
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyView view = controller.process(req, resp);
view.render(req, resp);
}
}
request와 response에 데이터를 받고 view로 경로를 받은 후에 실행해주는 것이다.
다음 리펙토링이다.
컨트롤러에서는 request와 response가 꼭 필요하지 않다.
어차피 데이터를 넘길거면 담아서 넘기는 게 아니라 그냥 넘기면 되기 때문이다.
그렇기 때문에 컨트롤러에서 서블릿 기술을 제외해보자.
스프링처럼 Model을 만들어 데이터를 넘기도록 한다.

이렇게 Controller에서 Model에 데이터만 담아서 가져오는 것이다.
package hello.servlet.web.frontcontroller;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.Map;
@Getter
@Setter
public class ModelView {
private String viewName;
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName, Map<String, Object> model) {
this.viewName = viewName;
}
}
이렇게 Map으로 key, value로 데이터들을 저장하도록 만든다.
그러고 경로를 저장하기 위해서 viewName을 사용한다.
이제 ModelView를 리턴하도록 코드들을 바꾸어보자.
package hello.servlet.web.frontcontroller.version3;
import hello.servlet.web.frontcontroller.ModelView;
import java.util.Map;
public class StudentFormControllerV3 implements ControllerV3{
@Override
public ModelView process(Map<String, String> paramMap) {
return new ModelView("new-form");
}
}
이렇게 Model에 jsp의 경로와 데이터들을 넣어준다.
package hello.servlet.web.frontcontroller.version3;
import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import hello.servlet.web.frontcontroller.ModelView;
import java.util.Map;
public class StudentSaveControllerV3 implements ControllerV3{
private StudentRepository studentRepository = StudentRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
String studentName = paramMap.get("studentName");
int year = Integer.parseInt(paramMap.get("year"));
Student student = new Student(studentName, year);
studentRepository.save(student);
ModelView modelView = new ModelView("save-result");
modelView.getModel().put("student", student);
return modelView;
}
}
package hello.servlet.web.frontcontroller.version3;
import hello.servlet.domain.student.Student;
import hello.servlet.domain.student.StudentRepository;
import hello.servlet.web.frontcontroller.ModelView;
import java.util.List;
import java.util.Map;
public class StudentListControllerV3 implements ControllerV3{
private StudentRepository studentRepository = StudentRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
List<Student> students = studentRepository.findAll();
ModelView modelView = new ModelView("students");
modelView.getModel().put("students", students);
return modelView;
}
}
이제 이렇게 작동할 FrontController를 작성한다.
package hello.servlet.web.frontcontroller.version2;
import hello.servlet.web.frontcontroller.ModelView;
import hello.servlet.web.frontcontroller.MyView;
import hello.servlet.web.frontcontroller.version2.controller.StudentFormControllerV2;
import hello.servlet.web.frontcontroller.version2.controller.StudentListControllerV2;
import hello.servlet.web.frontcontroller.version2.controller.StudentSaveControllerV2;
import hello.servlet.web.frontcontroller.version3.ControllerV3;
import hello.servlet.web.frontcontroller.version3.StudentFormControllerV3;
import hello.servlet.web.frontcontroller.version3.StudentListControllerV3;
import hello.servlet.web.frontcontroller.version3.StudentSaveControllerV3;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
private Map<String, ControllerV3> controllerMap = new HashMap<>();
public FrontControllerServletV3(){
controllerMap.put("/front-controller/v3/students/new-form", new StudentFormControllerV3());
controllerMap.put("/front-controller/v3/students/save", new StudentSaveControllerV3());
controllerMap.put("/front-controller/v3/students", new StudentListControllerV3());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ControllerV3 controller = controllerMap.get(req.getRequestURI());
if(controller == null){
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> paramMap = createParamMap(req);
ModelView modelView = controller.process(paramMap);
String viewName = modelView.getViewName();
MyView view = viewer(viewName);
view.render(modelView.getModel(), req, resp);
}
private MyView viewer(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
private Map<String, String> createParamMap(HttpServletRequest req) {
Map<String, String> paramMap = new HashMap<>();
req.getParameterNames().asIterator()
.forEachRemaining(paramName -> paramMap.put(paramName, req.getParameter(paramName)));
return paramMap;
}
}
여기에 MyView에 파라미터가 3개 들어가는 생성자가 호출 되었으니, 그에 맞게 생성자를 추가해준다.