안녕하세요, 오늘은 MVC 패턴에 대해 정리해보았습니다.
MVC 패턴 개요
ex) 비지니스 로직이 존재하는 파일에 HTML이 엄청나게 많다면..?
@WebServlet("/hello-world")
public class HelloWorldServlet extends HttpServlet {
public HelloWorldServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charaset=utf-8");
PrintWriter out = response.getWriter();
// 화면을 그리는 코드 START
out.println("<h1>Hello World!</h1>");
// 화면을 그리는 코드 END
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
HttpServletRequest, HttpServletResponse객체를 생성합니다.doGet() 혹은 doPost() 등의 메서드를 호출합니다.HttpServletResponse 객체에 응답을 담아 Client(브라우저)에 반환합니다.HttpServletRequest, HttpServletResponse객체를 소멸합니다.템플릿 엔진이 나온 이유
자바 코드로 HTML을 만들어 내는 것이 아닌 HTML 문서에 동적으로 변경해야 하는 부분만
자바 코드를 넣을 수 있다면 더 편리하다.
대표적인 예
JSP(Java Server Pages)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!-- HTML 코드... -->
<%
String url = null;
// 선택한 사이트를 String으로 받아온다.
String site = request.getParameter("site");
// 선택한 사이트가 스파르타라면
if(site.equals("sprata")) {
url="teamSparta.jsp";
// 스파르타가 아니라도 못 빠져나간다.
} else {
url="teamSparta.jsp";
}
%>
<!-- 위에서 지정된 페이지로 이동한다. -->
<!-- JSP 코드... -->
<!-- HTML 코드... -->
<!-- JSP 코드... -->
<!-- HTML 코드... -->
<!-- JSP 코드... -->
<!-- HTML 코드... -->
<jsp:forward page="<%=url %>" />
</body>
</html>
Servlet과 JSP

비지니로직
비밀번호 검증 → 특수문자 한개포함, 대문자 한개포함
비밀번호 검증 → 특수문자 두개포함, 대문자 세개포함.
비지니스 로직과 View는 대부분 수정이 함께 일어나지 않고 각자 발생한다.
(서로에게 연관이 거의 없다, 동시에 변하는 경우는 기획이 변하는 경우)
연관 없는 코드끼리 함께 존재하는 이유가 무엇인가?
JSP같은 View Template 들은 화면을 Rendering하는 역할만 수행하는것이 가장 효과적이다.
MVC는 하나의 Servlet이나 JSP로 처리하던 것들을 Controller, View 영역으로 나눈것이다.
Controller, Service, Repository → MVC 이후의 일.

디렉토리 구조

build.gradle 의존성 추가
// Spring Boot 3.0 미만
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'
// Spring Boot 3.0 이상
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'jakarta.servlet:jakarta.servlet-api'
implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api'
implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl'
Post(블로그 글) Object
@Getter
@Setter
public class Post{
// PK
private Long id;
// 제목
private String title;
// 내용
private String content;
public Post() {}
public Post(String title, String content) {
this.title = title;
this.content = content;
}
}
블로그 작성 버튼 클릭(생략) → 블로그 작성 폼 Servlet
@WebServlet(
name = "mvcPostFormServlet",
urlPatterns = "/mvc/posts/form"
)
public class MvcPostFormServlet extends HttpServlet {
@Override
protected void service(
HttpServletRequest request,
HttpServletResponse response
) throws ServletException, IOException {
// webapp/WEB-INF/views -> WEB-INF 내부의 파일은 외부에서 호출할 수 없다.
String path = "/WEB-INF/views/postForm.jsp";
// 이때 Controller에서 View로 이동
RequestDispatcher dispatcher = request.getRequestDispatcher(path);
// forward : Servlet에서 다른 Servlet이나 jsp 호출
dispatcher.forward(request, response);
}
}
// 참고
@Controller
public class PostController {
// Service 생성자 주입
@PostMapping("/posts")
public void create(@RequestBody RequestDTO dto) {
service.create(dto);
return "post";
}
}
블로그 작성 Form JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
<title>블로그 포스트 작성 페이지/title>
</head>
<body>
<h1>블로그 글쓰기</h1>
<form action="save" method="post">
title: <input type="text" name="title" placeholder="제목" />
content: <input type="text" name="content" placeholder="내용" />
<button type="submit">저장</button>
</form>
</body>
</html>
블로그 작성 폼 Submit → 블로그 글 저장 Servlet
@WebServlet(
name = "mvcPostSaveServlet",
urlPatterns = "/mvc/posts/save"
)
public class MvcPostSaveServlet extends HttpServlet {
// Singleton
private PostRepository postRepository = PostRepository.getInstance();
@Override
protected void service(
HttpServletRequest request,
HttpServletResponse response
) throws ServletException, IOException {
String title= request.getParameter("title");
String content = request.getParameter("content");
Post post = new Post(title, content);
postRepository.save(post);
// Model에 Data를 보관한다.
// request 내부 임시 저장소에 post저장
request.setAttribute("post", post); // MODEL
String path = "/WEB-INF/views/save-result.jsp"
RequestDispatcher dispatcher = request.getRequestDispatcher(path);
disPatcher.forward(request, response);
}
}
PostRepository
public class PostRepository {
private static Map<Long, Post> database = new HashMap<>();
private static long incrementId = 0L;
private static final PostRepository instance = new PostRepository();
public static PostRepository getInstance() {
return instance;
}
private PostRepository() {}
// Post 저장
public Post save(Post post) {
post.setId(++incrementId);
database.put(post.getId(), post);
return post;
}
// Post 전체 조회
public List<Post> findAll() {
return new ArrayList<>(database.values());
}
}
저장결과(save-result.jsp) JSP → View
<!-- request 객체 안에 setAttribute로 post를 임시 저장했음 -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<h1>글작성 성공!!!</h1>
<ul>
<li>id=<%=post.getId()%></li>
<li>title=<%=post.getTitle()%></li>
<li>content=<%=post.getContent()%></li>
</ul>
<a href="/index.html">홈 화면으로 돌아가기</a>
</body>
</html>
블로그 글 목록조회 Servlet
@WebServlet(
name = "mvcPostListServlet",
urlPatterns = "/mvc/posts"
)
public class MvcPostListServlet extends HttpServlet {
private PostRepository postRepository = PostRepository.getInstance();
@Override
protected void service(
HttpServletRequest request,
HttpServletResponse response
) throws ServletException, IOException {
// 회원 목록 조회
List<Post> posts = postRepository.findAll();
// request 객체의 임시저장소 Model에 posts를 저장
request.setAttribute("posts", posts);
String path = "/WEB-INF/views/posts.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(path);
dispatcher.forward(request, response);
}
}
블로그 글 목록조회 JSP(posts.jsp)
<%@ 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>
</head>
<body>
<a href="/index.html">홈 화면으로 돌아가기</a>
<table>
<thead>
<th>id</th>
<th>title</th>
<th>content</th>
</thead>
<tbody>
<%
for (Post post : posts) {
out.write("<tr>");
out.write(" <td>" + post.getId() + "</td>");
out.write(" <td>" + post.getTitle() + "</td>");
out.write(" <td>" + post.getContent() + "</td>");
out.write("</tr>");
}
%>
</tbody>
</table>
</body>
</html>
우리는 MVC가 적용되며 Controller와 View가 분리된것을 보았다.
이제 화면에 수정이 발생하면 화면(View)만 수정하고
로직에 수정이 발생하면 로직만 수정하여 배포하면 된다.

dispatcher.forward(request, response) View로 이동하는 forward가 항상 중복 호출된다.String path= “/WEB-INF/views/new-form.jsp” View의 path를 입력(중복 작업)한다.HttpServletResponse 객체를 사용하는 경우가 적다. (JSP에서 모두 해결하기 때문)
단순히 공통 기능을 Method로 분리하면 될것같지만
이것 또한 항상 중복적으로 호출이 필요하다.
이때까지만 해도 요청이 들어오는 입구가 너무 많다!
Servlet이 호출되기 전에 공통 기능을 처리 해주는것이 바로
프론트 컨트롤러 패턴이다.(입구가 하나)
프론트 컨트롤러 패턴

퍼즐 예시

오늘의 코드카타
class Solution {
public int[] solution(int start_num, int end_num) {
int[] answer = new int[end_num - start_num + 1];
for (int i = 0; i < answer.length; i++) {
answer[i] = start_num + i;
}
return answer;
}
}
시작 넘버부터 종료 넘버까지 배열에 담기