Spring Boot는 기본적으로 Thymeleaf를 권장하지만, 레거시 프로젝트나 특정 요구사항에 따라 JSP를 사용해야 할 때도 있습니다. 이 글에서는 Spring Boot에서 JSP를 설정하고 활용하는 방법을 알아봅니다.

JSP 설정

의존성 추가

Gradle:

implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'

Maven:

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>

View Resolver 설정

application.properties:

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

JSP 파일 위치

src/main/webapp/WEB-INF/jsp/login.jsp

주의: src/main/webapp 디렉토리가 클래스패스에 포함되어야 합니다.

Expression Language (EL)

JSP에서 Model 데이터에 접근하려면 Expression Language를 사용합니다:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Welcome</title>
</head>
<body>
    <h1>안녕하세요, ${name}님!</h1>
</body>
</html>

JSTL (JSP Standard Tag Library)

태그 라이브러리 선언

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>

반복문 (forEach)

<table>
    <thead>
        <tr>
            <th>설명</th>
            <th>마감일</th>
            <th>상태</th>
        </tr>
    </thead>
    <tbody>
        <c:forEach items="${todos}" var="todo">
            <tr>
                <td>${todo.desc}</td>
                <td>${todo.targetDate}</td>
                <td>${todo.status}</td>
            </tr>
        </c:forEach>
    </tbody>
</table>

조건문 (if, choose)

<c:if test="${not empty message}">
    <div class="alert">${message}</div>
</c:if>

<c:choose>
    <c:when test="${todo.status == 'DONE'}">
        <span class="badge badge-success">완료</span>
    </c:when>
    <c:otherwise>
        <span class="badge badge-warning">진행중</span>
    </c:otherwise>
</c:choose>

Spring Form 태그

Form 태그 라이브러리 선언

<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>

기본 Form 구조

<form:form method="post" modelAttribute="todo">
    <fieldset class="form-group">
        <form:label path="desc">설명</form:label>
        <form:input path="desc" type="text" class="form-control" required="required"/>
    </fieldset>

    <fieldset class="form-group">
        <form:label path="targetDate">마감일</form:label>
        <form:input path="targetDate" type="text" class="form-control"/>
    </fieldset>

    <button type="submit" class="btn btn-primary">저장</button>
</form:form>

Controller에서 Form 처리

@Controller
public class TodoController {

    @GetMapping("/add-todo")
    public String showAddTodoPage(ModelMap model) {
        model.addAttribute("todo", new Todo(0, "", "Default Desc", new Date(), false));
        return "todo";
    }

    @PostMapping("/add-todo")
    public String addTodo(ModelMap model, Todo todo) {
        // Todo 저장 로직
        service.addTodo(todo);
        return "redirect:/list-todos";
    }
}

Hidden 필드

Form에서 보이지 않지만 submit 시 전송되어야 하는 값:

<form:form method="post" modelAttribute="todo">
    <form:hidden path="id"/>
    <!-- 다른 필드들 -->
</form:form>

서버 사이드 유효성 검증

Entity에 유효성 어노테이션 추가

public class Todo {
    private int id;
    private String user;

    @Size(min = 10, message = "최소 10자 이상 입력해주세요.")
    private String desc;

    @NotNull(message = "마감일은 필수입니다.")
    @Future(message = "마감일은 미래 날짜여야 합니다.")
    private Date targetDate;
}

Controller에서 검증 처리

@PostMapping("/add-todo")
public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) {

    if (result.hasErrors()) {
        return "todo"; // 에러가 있으면 폼 페이지로 돌아감
    }

    service.addTodo(todo);
    return "redirect:/list-todos";
}

JSP에서 에러 메시지 표시

<fieldset class="form-group">
    <form:label path="desc">설명</form:label>
    <form:input path="desc" type="text" class="form-control" required="required"/>
    <form:errors path="desc" cssClass="text-danger"/>
</fieldset>

날짜 포맷팅

Controller에서 날짜 바인딩 설정

@Controller
public class TodoController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }
}

JSP에서 날짜 포맷팅 (Form 외부)

<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>

<td>
    <fmt:formatDate value="${todo.targetDate}" pattern="yyyy-MM-dd"/>
</td>

Date Picker 연동

의존성 추가:

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>bootstrap-datepicker</artifactId>
    <version>1.0.1</version>
</dependency>

JSP에서 사용:

<script src="webjars/bootstrap-datepicker/1.0.1/js/bootstrap-datepicker.js"></script>

<form:input path="targetDate" id="targetDate" type="text" class="form-control"/>

<script>
    $('#targetDate').datepicker({
        format: 'dd/mm/yyyy'
    });
</script>

JSP Fragment (공통 코드 분리)

Header Fragment (header.jspf)

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>

<nav class="navbar navbar-default">
    <div class="container">
        <a class="navbar-brand" href="/">My App</a>
        <ul class="nav navbar-nav navbar-right">
            <li><a href="/logout">Logout</a></li>
        </ul>
    </div>
</nav>

Fragment 포함

<%@ include file="common/header.jspf" %>

<div class="container">
    <h1>Todo List</h1>
    <!-- 내용 -->
</div>

<%@ include file="common/footer.jspf" %>

결론

Spring Boot에서 JSP를 사용하려면 추가 설정이 필요하지만, JSTL과 Spring Form 태그를 활용하면 강력한 웹 페이지를 구축할 수 있습니다. 서버 사이드 유효성 검증을 통해 데이터 무결성을 보장하고, Fragment를 활용하여 코드 중복을 줄이세요. 다만, 새로운 프로젝트에서는 Thymeleaf 사용을 권장합니다.