01. 프레임워크 만들기
MVC pattern 의 효율성을 체감하기 위해 코드를 단계적으로 리팩토링하면서 Spring MVC와 유사한 프레임워크를 만들어보자. (v1 ~ v5 까지의 과정을 거쳐 만들어나갈 것이다.)
1️⃣ V1
출처: 김영한의 스프링MVC (인프런)
💡 기존 코드에서는 하나의 서블릿이 model, view, controller 의 모든 역할을 수행하고 있었기 때문에 코드의 가독성, 재사용성이 떨어지는 단점이 있었다. (유지보수가 험난해짐…) v1 에서는 controller 의 역할을 분리하기로 했다.
⚡️ 1-1. FrontControllerServletV1
- 클라이언트가 요청하는 uri 를 각각의 컨트롤러로 매핑해줄 FrontController 를 만들었다.
- 해당하는 컨트롤러를 호출하고, process 메서드를 실행시킨다.
// * : v1 패키지 밑에 어떤 주소가 들어와도 해당 클래스의 서블릿이 호출된다는 뜻.
@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/members/new-form", new MemberFormControllerV1());
controllerMap.put("/front-controller/v1/members/save", new MemberSaveControllerV1());
controllerMap.put("/front-controller/v1/members", new MemberListControllerV1());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV1.service");
String requestURI = request.getRequestURI(); //요청주소 uri 를 그대로 받아올 수 있다.
ControllerV1 controller = controllerMap.get(requestURI); //맵에서 해당 uri 의 구현객체를 꺼내온다.
if(controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND); //웹 상태코드를 404로 바꾸고 리턴.
return;
}
//해당 구현객체의 process 메소드를 실행한다. 다형성!!
controller.process(request, response);
}
}
⚡️ 1-2. ControllerV1
기본구조에서 했던대로 각 기능별(uri)로 컨트롤러를 매핑해줄 것이다. 다만 공통적으로 사용되는 HttpServletRequest, HttpServletResponse 를 ControllerV1 인터페이스에 정의하여 각 컨트롤러별로 오버라이딩하여 사용하도록 만들었다.
다형성의 장점은 로직의 일관성을 유지하여 변경을 최소화할 수 있다는 데 있다. 본 예제처럼 FrontController에서 process() 메서드를 호출하면 각 구현객체별로 메서드를 오버라이딩하여 비즈니스 로직을 수행하게 된다.
public interface ControllerV1 {
//여러개의 컨트롤러를 만들거라서 인터페이스의 다형성을 활용한다.
//프론트 컨트롤러가 이 인터페이스를 호출하면서 로직의 일관성을 가져간다.
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
⚡️ 1-3. 구현체 컨트롤러들…
- ControllerV1 인터페이스를 상속받아 오버라이딩하는 형태로 3개의 개별 컨트롤러들을 만들었다.
public class MemberFormControllerV1 implements ControllerV1 {
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
public class MemberSaveControllerV1 implements ControllerV1 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
request.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
public class MemberListControllerV1 implements ControllerV1 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
String viewPath = "/ERB-INF/views/members.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request,response);
}
}
🤔💭 View 로 전송하는 dispatcher 메서드가 겹치는데..? 이걸 개선할 수 없을까?