웹 제작 변천기 - MVC 패턴이란?
1. 서블릿이 주요 로직, 뷰 렌더링 모두 실행
•
자바 코드에 HTML 태그를 넣어 정의하는 형식
•
HTML 코드를 적기 힘들고 복잡함
@WebServlet(name = "memberFormServlet", urlPatterns = "/servlet/members/new-form")
public class MemberFormServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
PrintWriter w = resp.getWriter();
w.write("<!DOCTYPE html>\n" +
"<html>\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>Title</title>\n" +
"</head>\n" +
"<body>\n" +
"<form action=\"/servlet/members/save\" method=\"post\">\n" +
" username: <input type=\"text\" name=\"username\" />\n" +
" age: <input type=\"text\" name=\"age\" />\n" +
" <button type=\"submit\">전송</button>\n" +
"</form>\n" +
"</body>\n" +
"</html>\n");
}
}
Java
복사
2. JSP와 같은 뷰 템플릿으로 로직, 뷰 렌더링 모두 실행
•
동적 화면이 필요한 부분에만 자바코드를 넣는 방식으로 HTML 작업이 깔끔해 졌다.
•
jsp에 많은 기능을 넣어 유지보수가 힘들다.
•
변경의 라이프 사이클이 다른 기능들이 하나의 파일(코드)에서 관리된다.
<% %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%
//request, response 사용 가능
MemberRepository memberRepository = MemberRepository.getInstance();
System.out.println("MemberSaveServlet.service");
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
%>
<html>
<head>
<title>Title</title>
</head>
<body>
<ul>
<li>id=<%=member.getId()%></li>
<li>username=<%=member.getUsername()%></li>
<li>age=<%=member.getAge()%></li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
Java
복사
3. MVC 패턴
•
역할을 나눠 코드를 작성하는 방법
•
컨트롤러 - HTTP 요청을 받아서 파라미터 검증, 비즈니스 로직 실행, 뷰에 전달할 결과 데이터 조회해서 모델에 담기
모델 - 뷰에 출력할 데이터를 담아둠
뷰 - 모델에 담겨 있는 데이터를 사용해서 화면을 생성
서비스 - 비즈니스 로직 코드
•
역할을 분담하는 목적을 확실히 달성하였지만 아직 단점이 있다.
◦
View로 이동하는 코드가 중복 호출/ 직접 호출
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
Java
복사
◦
사용하지 않는 코드가 생김 - HttpServletRequest , HttpServletResponse
심지어 저 코드를 사용하면 테스트 케이스 작성도 힘듬
public class MvcMemberListServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
Java
복사
MVC Front 패턴
위와 같이 MVC 패턴이 지닌 단점을 보완하고자 등장했다.
주요 아이디어는 코드의 중복을 일으키는 공통 기능을 모아서 앞단에서 먼저 해결하자는 것이다. (수문장 역할)
프론트 컨트롤러 패턴 특징
•
프론트 컨트롤러 서블릿 하나로 클라이언트 요청을 받는다. (다 모아 ~~~)
요청 입구를 하나로 만듦으로써 컨트롤러의 공통 기능을 처리 가능!
•
프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출해준다.
프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 된다.
요청은 프론트 컨트롤러가 받으며, 해당 내용을 파라미터 or 객체로 넘겨 주면 되니까.
•
스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있다!
프론트 컨트롤러 패턴으로 MVC 구현
/**
* 각각의 컨트롤러는 View 객체를 생성해서 넘기고,
* 프론트 컨트롤러는 view.render() 해서 jsp파일 (뷰 템플릿 파일) 실행
*/
@WebServlet(name = "frontController", urlPatterns = "/front-controller/*")
public class FrontController extends HttpServlet {
private Map<String, Controller> controllerMap = new HashMap<>();
// frontController 객체가 생성되면, 각 컨트롤러들을 실행시킬 수 있도록 Map 안에 넣는다.
// key = uri, value = 컨트롤러
// Member__Controller들은 Controller interface의 구현체들
public FrontController() {
controllerMap.put("/front-controller/members/new-form", new MemberFormController());
controllerMap.put("/front-controller/members/save", new MemberSaveController());
controllerMap.put("/front-controller/members", new MemberListController());
}
// Httpservlet 오버라이드
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// request 요청 uri 변수화
String requestURI = request.getRequestURI();
Controller controller = controllerMap.get(requestURI); // controller == 인터페이스
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyView view = controller.process(request,response);
view.render(request,response); // jsp 실행
}
}
Java
복사
public interface Controller {
MyView process(HttpServletRequest request, HttpServletResponse response)
throws ServletException, 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 dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
Java
복사
위 아키텍쳐를 어댑터 개념까지 도입해서 추상화하면,
이런 구조를 가지게 된다.
DispatcherServlet
스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있다!
동작원리
•
DispacherServlet은 부모 클래스에서 HttpServlet을 상속 받아서 사용하고, 서블릿으로 동작한다.
DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
Plain Text
복사
•
스프링 부트는 DispacherServlet을 서블릿으로 자동으로 등록하면서 모든 경로( urlPatterns="/" )에 대해서 매핑한다.
요청 흐름
1.
서블릿 호출 - HttpServlet 의 serivce() 호출
2.
부모 클래스에서 오버라이드한 service()를 비롯한 여러 메서드 호출
3.
DispacherServlet.doDispatch() 호출
이 메서드의 코드는 위에서 만든 코드와 유사 하다.
동작순서
a.
핸들러 조회
b.
핸들러 어댑터 조회
c.
핸들러 어댑터 실행
d.
핸들러 실행
e.
ModelView 반환
f.
viewResolver 호출
g.
View 반환
h.
뷰 렌더