Ⅰ. Template Engine
📚 동적 웹 페이지 생성 도구. 템플릿을 기반으로 정적인 부분과 동적인 데이터 결합으로 HTML, XML 등 문서생성 역할 → UI(User Interface)를 만들며, SSR(Server Side Rendering)에 사용

Ⅱ. MVC 패턴 개요
Servlet이나 JSP만으로 비지니스 로직과 View Rendering 까지 모두 처리하면 너무 많은 역할을 하게 되고 유지보수가 굉장히 어려워져서(책임이 너무 많음) 고안된 패턴이다. Web Application은 일반적으로 MVC(Model View Controller) 패턴을 사용한다.
→ 백엔드 웹 기술의 역사 첫 번째
@WebServlet("/hello-world")
public class HelloWorldServlet extends HttpServlet {
// User 저장소
private UserRepository repository = new UserRepository();
public HelloWorldServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
try {
// 비지니스 로직을 처리하는 코드
// 파라미터 조회
String userIdParam = request.getParameter("userId");
Long userId = null;
if (userIdParam != null) {
userId = Long.parseLong(userIdParam);
}
// 회원 조회
String userInfo = repository.findById(userId);
// 화면을 그리는 코드 START
out.println("<h1>Hello World!</h1>");
out.println("<div>조회한 회원의 정보: " + userInfo + "</div>");
// 화면을 그리는 코드 END
} catch (NumberFormatException e) {
// parsing 에러가 발생한 경우
out.println("<div>Invalid user ID format</div>");
} finally {
out.close();
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
Servlet 문제점
Servlet 동작 순서


사용자가 Client(브라우저)를 통해 서버에 HTTP Request 즉, API 요청을 한다.
요청을 받은 Servlet 컨테이너는 HttpServletRequest, HttpServletResponse객체를 생성한다.
설정된 정보(URL, HTTP Method)를 통해 어떠한 Servlet에 대한 요청인지 찾는다.
해당 Servlet에서 service 메서드를 호출한 뒤 브라우저의 요청 Method에 따라 doGet() 혹은doPost() 등의 메서드를 호출한다.
서버에서 응답을 생성한 뒤 HttpServletResponse객체에 응답을 담아 Client(브라우저)에 반환한다.
응답이 완료되면 생성한 HttpServletRequest, HttpServletResponse객체를 소멸한다.
<%@ 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 코드... -->
<!-- <%...%> 영역에는 Java 코드를 사용할 수 있다. -->
<%
// 게시글 저장소 싱글톤으로 인스턴스 생성
BoardRepository boardRepository = BoardRepository.getInstance();
// 게시글 제목, 내용
String title = request.getParameter("title");
String content = request.getParameter("content");
// 게시글 객체 생성
Board board = new Baord(title, content);
// 게시글 객체 저장
repository.save(board);
%>
<div>
ID : <input type ="text" name = "id" value="<%=id %>">
</div>
<!-- JSP 코드... -->
<!-- HTML 코드... -->
<jsp:forward page="<%=url %>" />
</body>
</html>
Servlet 코드에서 HTML을 만드는 부분인 View가 분리되었다.
Servlet과 JSP 구조

Ⅲ. MVC 패턴
📚 하나의 Servlet이나 JSP로 처리하던 것들을 Model, View, Controller 영역으로 나눈것이다.
→ 백엔드 웹 기술의 역사 두번째

Controller
Model
View
Ⅳ. MVC 패턴의 문제점
MVC 패턴을 적용 후 View의 역할은 필요한 데이터를 Model 에서 참조하여 화면을 그리는 역할만 수행하면 된다. 하지만 Controller에 해당하는 부분은 여전히 문제를 가지고 있다.
MVC 패턴 적용

문제점
dispatcher.forward(request, response) View로 이동하는 forward가 항상 중복 호출된다.String path= “/WEB-INF/views/new-form.jsp” View의 path를 입력(중복 작업)한다.HttpServletResponse 객체를 사용하는 경우가 적다. (JSP에서 모두 해결하기 때문)공통 기능 처리
모든 컨트롤러에서 공통으로 적용되는 기능을 뜻한다.
ex) Log 출력, 인증, 인가 등

공통 기능을 Method로 분리하여 각각의 컨트롤러에서 사용하면 되는것 아닌가요?
공통 기능으로 만들어놓은 Method 또한 항상 중복적으로 호출이 필요합니다. 또한, 사람인 개발자가 작업하다보면 Method를 호출하는 일을 깜빡 할수도 있고 Method가 많아지면 많아질수록 Controller의 책임이 점점 커지겠죠? Method를 분리하여도 여전히 해결하지 못하는 문제점으로 남습니다. 과연 어떻게 이런 문제점들을 해결할 수 있을까요? 프론트 컨트롤러 패턴에 대해 알아봅시다.
Ⅴ. 프론트 컨트롤러 패턴
Servlet(Controller)이 호출되기 전에 공통 기능을 하나의 Servlet에서 처리해주는 패턴이다. 프론트 컨트롤러(Servlet) 하나에 모든 클라이언트측 요청이 들어온다.
→ 입구가 오직 하나, 프론트 컨트롤러(Servlet)에서 공통 기능을 처리하면 된다.
프론트 컨트롤러 패턴 구조

프론트 컨트롤러의 역할
HttpServlet을 상속받거나, @WebServlet을 사용하지 않아도 된다.프론트 컨트롤러 의문점

ooo oo이다!Ⅵ. 어댑터 패턴
다양한 컨트롤러(Handler)를 유연하게 만들기위해 어댑터 패턴을 도입하게 되었다. 컨트롤러들은 동일한 인터페이스를 구현하도록 하고 해당 인터페이스와 공통 로직 사이에 어댑터를 두어 유연하게 만든다. 서로 다른 인터페이스를 갖는 두 클래스를 연결해주는 패턴이다.
어댑터란?
다른 전기나 기계 장치를 서로 연결해서 작동할 수 있도록 만들어 주는 결합 도구다.

1. 컨트롤러(Handler)는 비지니스 로직을 처리하고 알맞은 결과를 반환한다.
2. 어댑터는 공통 로직과 컨트롤러(Handler)가 자연스럽게 연결되도록 한다.
3. 프론트 컨트롤러는 공통으로 처리되는 로직을 수행한다.
어댑터 패턴 장점
요약
📚 MVC 패턴을 적용한 JSP를 사용하게되면 화면을 그려내는 View 부분과 비지니스 로직을 실행하는 부분의 역할이 구분된다.
→ 백엔드 웹 기술의 역사 세번째
디렉토리 구조

src/main/java
webApp/WEB-INF/views
build.gradle 의존성 추가
// Spring Boot 3.0 미만
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
// 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'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
package com.example.springbasicjsp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan
public class SpringBasicJspApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBasicJspApplication.class, args);
}
}

package com.example.springbasicjsp;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Post {
private Long id;
private String title;
private String content;
public Post(String title, String content) {
this.title = title;
this.content = content;
}
}
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());
}
}
package com.example.springbasicjsp;
import jakarta.servlet.RequestDispatcher;
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;
@WebServlet(
name = "mvcPostFormServlet",
urlPatterns = "/mvc/posts"
)
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/post-form.jsp";
// 이때 Controller에서 View로 이동
RequestDispatcher dispatcher = request.getRequestDispatcher(path);
// forward : Servlet에서 다른 Servlet이나 jsp 호출
dispatcher.forward(request, response);
}
}
webapp/WEB-INF/views/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="posts/save" method="post">
title: <input type="text" name="title" placeholder="제목" />
content: <input type="text" name="content" placeholder="내용" />
<button type="submit">저장</button>
</form>
</body>
</html>
http://localhost:8080/mvc/posts
package com.example.springbasicjsp;
import jakarta.servlet.RequestDispatcher;
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;
@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);
}
}
webapp/WEB-INF/views/save-result.jsp<!-- 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>
</body>
</html>


package com.example.springbasicjsp;
import jakarta.servlet.RequestDispatcher;
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.List;
@WebServlet(
name = "mvcPostListServlet",
urlPatterns = "/mvc/posts/list"
)
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);
}
}
<%@ 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>
<table>
<thead>
<th>id</th>
<th>title</th>
<th>content</th>
</thead>
<tbody>
<c:forEach var="post" items="${posts}">
<tr>
<td>${post.id}</td>
<td>${post.title}</td>
<td>${post.content}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
블로그 글 목록 조회 Servlet 호출
글 추가

- 추가성공

- 글목록조회
결과