
서블릿으로 개발시엔 HTML 코드를 JAVA 코드로 만들어야 했기 때문에 매우 번거롭고 코드가 지저분해졌었다. 이에따라 JSP를 사용하면서 HTML 코드와 자바 코드를 한 파일 안에 모두 적고 중간중간 동적으로 변경이 필요한 부분에만 자바 코드를 적용해서 코드가 한결 깔끔해졌지만, 또 발생한 문제가 JSP가 너무 많은 기능을 담당하게 된다는 것이다.
서블릿만으로 만든 앱, JSP만으로 만든 앱은 생략
이전에 하나의 서블릿과 JSP만으로 개발을 해보았을 때, 당연히 예제여서 코드가 몇 줄에 지나지 않았지만 실무에서는 몇 천줄이 넘기 부지기수다. 이렇게 되면 유지보수가 상당히 어려워 진다.
설계에 있어서 변경주기가 다르면 파일을 분리하는게 맞다.
UI를 일부 수정하는 일과 비즈니스 로직을 수정하는 일은 각각 다르게 발생할 가능성이 매우 높다.
JSP 같은 뷰 템플릿은 화면을 렌더링 하는데 최적화 되어 있기 때문에 이부분의 업무만 담당하는 것이 가장 효과적이다.
기존에 하나의 서블릿,JSP를 기능 별로 분류하여 컨트롤러, 뷰 영역으로 분리한 것을 말한다.
HTTP 요청을 받아서 파라미터 검증을 하고, 비즈니스 로직을 실행한다. 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다
뷰에 출력할 데이터를 담아둔다.
모델에 담겨있는 데이터로 화면을 출력한다. (HTML 생성)

이전에 서블릿과 JSP를 사용하여 개발 했을 때

컨트롤러와 서비스를 분화 하지 않은 경우 ( 컨트롤러가 너무 많은 일을 담당하게 됨)

컨트롤러는 서비스를 호출하는 역할만 하고 서비스계층을 별도로 분화한 경우
( 일반적인 MVC의 구조)
참고
컨트롤러에 비즈니스 로직을 둘 수도 잇지만, 이렇게 되면 컨트롤러가 너무 많은 역할을 담당한다. 그래서 일반적으로 비즈니스 로직은 서비스라는 계층을 별도로 만들어서 처리한다. 그리고 컨트롤러는 비즈니스 로직이 있는 서비스 호출을 담당한다. 참고로 비즈니스 로직을 변경 하려면 비즈니스 로직을 호출하는 컨트롤러의 코드도 변경될 수 있다.
서블릿을 컨트롤러로, JSP를 뷰로 적용 해본다.
Model 은 HttpServletRequest를 사용한다. request는 내부에 데이터 저장소를 가지고 있다
request.setAttribute(),request.getAttribute()를 사용하면 데이터를 보관하고, 조회할 수 있다.
@WebServlet(name="mvcMemberFormServlet", urlPatterns="/servlet-mvc/members/new-form")
public class MvcMemberFromController extneds HttpServlet{
@Override
private class Service(HttpRequest request, HttpResponse response) throws Exception{
String viewPath= "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequest.Dispatcher(viewPath);
dispacther.forward(request, response);
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 상대경로 사용, [현재 URL이 속한 계층 경로 + /save] -->
<form action="save" method="post">
username: <input type="text" name="username" />
age: <input type="text" name="age" />
<button type="submit">전송</button>
</form>
</body>
</html>
<form action="save" method="post">를 보면 save에 / 가 없는 것을 볼수 있다. 상대경로이다.
현재 계층 경로: /servlet-mvc/members/
결과: /servlet-mvc/members/save
@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/
save")
public class MvcMemberSaveServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(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);
System.out.println("member = " + member);
memberRepository.save(member);
request.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
} }
싱글톤 패턴으로 멤버래퍼지토리를 getInstance로 얻고,
request로 이름 나이를 받고 객체로 저장한 다음
다시 멤버에 나이, 이름을 넣고
save 한 뒤
컨트롤러 - > jsp로 포워딩 하고 있다.
dispatcher는 인터페이스로 forward가 구현체라고 생각하면 편하다
request의 임시 저장소 setAttribute, getAttribute를 사용하여 뷰에 전달하는 모습
model의 역할
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
</head>
<body> 성공 <ul>
<li>id=${member.id}</li>
<li>username=${member.username}</li>
<li>age=${member.age}</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
@WebServlet(name = "mvcMemberListServlet", urlPatterns = "/servlet-mvc/members")
public class MvcMemberListServlet extends HttpServlet {
private MemberRepository memberRepository =
MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("MvcMemberListServlet.service");
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
String viewPath = "/WEB-INF/views/members.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
findAll()로 멤버를 모두 담은 다음,setAttribute() 로 임시 저장 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/index.html">메인</a> <table>
<thead>
<th>id</th>
<th>username</th>
<th>age</th>
</thead>
<tbody>
<c:forEach var="item" items="${members}">
<tr>
<td>${item.id}</td>
<td>${item.username}</td>
<td>${item.age}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
JSP 에서는
<c:forEach var="item" items"${members}">기능을 사용하여 리스트의 요소들을 각기 꺼내서 동적으로 뷰에 보낼 수 있다.
내부에서 포워드 하거나, 서블릿을 거쳐서 ( 컨트롤러 ) 가야한다.
http://localhost:8080/WEB-INF/views/new-form.jsp처럼 직접 접근하려고 하면 whitelabel 오류가 발생한다.

리다이렉트는 실제 클라이언트(웹 브라우저)에 응답이 나갔다가 클라이언트가 redirect 경로로 다시 요청한다. 따라서 클라이언트가 먼저 인지하고, URL이 실제로 변경된다. (뷰가 새로고침) 반면에 포워드는 서버 내부에 일어나는 호출이기 때문에 클라이언트가 전혀 인지하지 못한다.
클라이언트 입장에서는 (서블릿 호출, 뷰 호출, 응답코드 호출) 응답을 한 번 받은 꼴이지만, 포워드는 여러번 받은 샘이 된다.
포워드는 서버 내부에서 일어나는 호출이므로 클라이언트가 인지하지 못한다.
MVC 패턴을 적용함으로서 컨트롤러의 역할과 뷰를 렌더링 하는 역할을 명확하게 굽누할 수 있었지만, 컨트롤러 부분에 중복되는 코드들이 너무 많이 보인다.
포워드 중복
View로 이동하는 코드가 항상 중복 호출되어야 한다. 물론 이 부분을 메서드로 공통화해도 되지만, 해당 메서드도 항상
직접 호출해야 한다.
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response); ```
**ViewPath에 중복**
String viewPath = "/WEB-INF/views/new-form.jsp";
prefix: `/WEB-INF/views/`
suffix: `.jsp`
그리고 만약 jsp가 아닌 thymeleaf 같은 다른 뷰로 변경한다면 전체 코드를 다 변경해야 한다
다음 코드를 사용할 때도 있고, 사용하지 않을 때도 있다. 특히 response는 현재 코드에서 사용되지 않는다.
HttpServletRequest request, HttpServletResponse response
그리고 이런 HttpServletRequest , HttpServletResponse 를 사용하는 코드는 테스트 케이스를 작성하기도 어렵다.
공통 처리가 어렵다.
기능이 복잡해질 수 록 컨트롤러에서 공통으로 처리해야 하는 부분이 점점 더 많이 증가할 것이다. 단순히 공통 기능을 메서드로 뽑으면 될 것 같지만, 결과적으로 해당 메서드를 항상 호출해야 하고, 실수로 호출하지 않으면 문제가 될 것이 다. 그리고 호출하는 것 자체도 중복이다.
공통 기능을 처리하는 대문 같은 장치가 필요하다
프론트 컨트롤러 패턴(Front Controller) : MVC의 핵심 키워드