반응형
2장. 개발 환경 설정
2.1 시스템 요구사항
Thymeleaf를 사용하기 위한 기본 요구사항은 다음과 같습니다:
필수 요구사항
- Java: JDK 8 이상 (권장: JDK 11 또는 17)
- Thymeleaf: 3.0.x 이상 (권장: 3.1.x)
- Spring Boot: 2.7.x 이상 (권장: 3.x)
지원 환경
- 웹 서버: Tomcat, Jetty, Undertow
- 빌드 도구: Maven 3.6+, Gradle 6.0+
- IDE: IntelliJ IDEA, Eclipse, Visual Studio Code
버전 호환성 매트릭스
Thymeleaf | Spring Boot | Java | 설명 |
---|---|---|---|
3.1.x | 3.0+ | 17+ | 최신 권장 조합 |
3.0.x | 2.7+ | 11+ | 안정적인 조합 |
2.1.x | 1.5+ | 8+ | 레거시 지원 |
2.2 Maven/Gradle 의존성 설정
Maven 설정
Spring Boot Starter 사용 (권장)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>thymeleaf-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
</properties>
<dependencies>
<!-- Thymeleaf Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 개발 도구 (선택사항) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 보안 (필요시) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Thymeleaf Security Integration -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<!-- 테스트 의존성 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
직접 의존성 관리
<dependencies>
<!-- Core Thymeleaf -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<!-- Spring Integration -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring6</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<!-- 추가 기능 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
</dependencies>
설명:
spring-boot-starter-thymeleaf
: 필요한 모든 Thymeleaf 의존성을 자동으로 포함spring-boot-devtools
: 템플릿 변경 시 자동 재시작 및 핫 리로드thymeleaf-extras-springsecurity6
: Spring Security와의 통합 지원thymeleaf-extras-java8time
: Java 8+ 시간 API 지원
Gradle 설정
Kotlin DSL
plugins {
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.0"
kotlin("jvm") version "1.9.0"
kotlin("plugin.spring") version "1.9.0"
}
group = "com.example"
version = "1.0-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17
repositories {
mavenCentral()
}
dependencies {
// Thymeleaf
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-web")
// 보안
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
// 개발 도구
developmentOnly("org.springframework.boot:spring-boot-devtools")
// 테스트
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
}
tasks.withType<Test> {
useJUnitPlatform()
}
Groovy DSL
plugins {
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
2.3 Spring Boot 프로젝트 설정
application.yml 설정
# 기본 서버 설정
server:
port: 8080
servlet:
context-path: /
# Thymeleaf 설정
spring:
thymeleaf:
# 템플릿 모드 설정
mode: HTML
# 템플릿 파일 위치
prefix: classpath:/templates/
suffix: .html
# 개발 시 캐시 비활성화
cache: false
# 인코딩 설정
encoding: UTF-8
# 템플릿이 존재하지 않을 때 예외 발생 여부
check-template: true
check-template-location: true
# 렌더링 전 템플릿 존재 확인
enable-spring-el-compiler: true
# 템플릿 해결 순서
template-resolver-order: 1
# 뷰 이름 패턴 (선택적)
view-names:
- "admin/*"
- "user/*"
# 제외할 뷰 이름 패턴 (선택적)
excluded-view-names:
- "error/*"
# 웹 설정
web:
resources:
# 정적 리소스 캐싱 (개발 시 비활성화)
cache:
cachecontrol:
max-age: 0
# 정적 리소스 위치
static-locations: classpath:/static/
# 메시지 소스 설정 (국제화)
messages:
basename: messages/messages
encoding: UTF-8
cache-duration: -1 # 개발 시 캐시 비활성화
# 로깅 설정
logging:
level:
org.thymeleaf: DEBUG # 개발 시에만 사용
org.springframework.web: DEBUG
root: INFO
# 프로파일별 설정
---
spring:
config:
activate:
on-profile: development
thymeleaf:
cache: false # 개발 환경에서 캐시 비활성화
devtools:
restart:
enabled: true
livereload:
enabled: true
---
spring:
config:
activate:
on-profile: production
thymeleaf:
cache: true # 프로덕션에서 캐시 활성화
logging:
level:
org.thymeleaf: WARN
org.springframework.web: WARN
설정 항목 설명:
cache: false
: 개발 중 템플릿 변경사항이 즉시 반영되도록 캐시 비활성화mode: HTML
: HTML5 템플릿 모드 (기본값)check-template-location
: 템플릿 파일 존재 여부 확인enable-spring-el-compiler
: Spring EL 컴파일러 활성화로 성능 향상
application.properties 설정 (대안)
# 서버 설정
server.port=8080
# Thymeleaf 기본 설정
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
spring.thymeleaf.check-template-location=true
# 정적 리소스 설정
spring.web.resources.static-locations=classpath:/static/
spring.web.resources.cache.cachecontrol.max-age=0
# 메시지 소스
spring.messages.basename=messages/messages
spring.messages.encoding=UTF-8
# 개발 도구
spring.devtools.restart.enabled=true
spring.devtools.livereload.enabled=true
# 로깅
logging.level.org.thymeleaf=DEBUG
logging.level.org.springframework.web=DEBUG
Java Configuration
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
@Configuration
public class ThymeleafConfig implements WebMvcConfigurer {
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver =
new SpringResourceTemplateResolver();
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setCacheable(false); // 개발 환경
templateResolver.setOrder(1);
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
// 추가 dialect 등록 (필요시)
// templateEngine.addDialect(new CustomDialect());
return templateEngine;
}
@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setOrder(1);
return viewResolver;
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(viewResolver());
}
}
Java Configuration 설명:
SpringResourceTemplateResolver
: Spring의 리소스 로딩 메커니즘 사용SpringTemplateEngine
: Thymeleaf 템플릿 엔진 설정ThymeleafViewResolver
: Spring MVC와의 통합을 위한 뷰 리졸버
2.4 IDE 설정
IntelliJ IDEA 설정
플러그인 설치
- File → Settings → Plugins
- Thymeleaf 플러그인 검색 후 설치
- Spring Boot 플러그인 확인 (보통 기본 설치됨)
프로젝트 설정
// IntelliJ IDEA 실행 설정
// Run/Debug Configurations → Spring Boot → VM options
-Dspring.profiles.active=development
-Dspring.devtools.restart.enabled=true
Live Templates 설정
<!-- th:text -->
th:text="${$VAR$}"
<!-- th:each -->
th:each="$ITEM$ : ${$COLLECTION$}"
<!-- th:if -->
th:if="${$CONDITION$}"
<!-- 완전한 Thymeleaf HTML 템플릿 -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${title}">$TITLE$</title>
</head>
<body>
<h1 th:text="${heading}">$HEADING$</h1>
$END$
</body>
</html>
Eclipse/STS 설정
Spring Tools 4 설치
- Help → Eclipse Marketplace
- Spring Tools 4 검색 후 설치
- Thymeleaf Plugin 설치 (선택사항)
프로젝트 Import
// Eclipse 실행 설정
// Run Configurations → Java Application → Arguments tab → VM arguments
-Dspring.profiles.active=development
-Dspring.devtools.restart.enabled=true
Visual Studio Code 설정
확장 프로그램 설치
{
"recommendations": [
"vscjava.vscode-spring-initializr",
"vscjava.vscode-spring-boot-dashboard",
"pivotal.vscode-spring-boot",
"wholroyd.jinja",
"ms-vscode.vscode-typescript-next"
]
}
settings.json 설정
{
"java.configuration.updateBuildConfiguration": "automatic",
"spring-boot.ls.problem.application-properties.unknown-property": "ignore",
"files.associations": {
"*.html": "html"
},
"emmet.includeLanguages": {
"thymeleaf": "html"
}
}
2.5 첫 번째 Thymeleaf 애플리케이션
프로젝트 구조
src/
├── main/
│ ├── java/
│ │ └── com/example/demo/
│ │ ├── DemoApplication.java
│ │ ├── controller/
│ │ │ └── HomeController.java
│ │ └── model/
│ │ └── User.java
│ └── resources/
│ ├── static/
│ │ ├── css/
│ │ │ └── style.css
│ │ └── js/
│ │ └── app.js
│ ├── templates/
│ │ ├── index.html
│ │ └── user/
│ │ └── profile.html
│ └── application.yml
└── test/
└── java/
└── com/example/demo/
└── DemoApplicationTests.java
메인 애플리케이션 클래스
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
모델 클래스
package com.example.demo.model;
import java.time.LocalDateTime;
import java.util.List;
public class User {
private Long id;
private String name;
private String email;
private String role;
private boolean active;
private LocalDateTime lastLogin;
private List<String> permissions;
// 생성자
public User() {}
public User(Long id, String name, String email, String role) {
this.id = id;
this.name = name;
this.email = email;
this.role = role;
this.active = true;
this.lastLogin = LocalDateTime.now();
}
// Getter/Setter 메소드
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
public LocalDateTime getLastLogin() { return lastLogin; }
public void setLastLogin(LocalDateTime lastLogin) { this.lastLogin = lastLogin; }
public List<String> getPermissions() { return permissions; }
public void setPermissions(List<String> permissions) { this.permissions = permissions; }
// 편의 메소드
public String getDisplayName() {
return name != null ? name : email;
}
public boolean isAdmin() {
return "admin".equalsIgnoreCase(role);
}
}
컨트롤러
package com.example.demo.controller;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
// 기본 데이터 설정
model.addAttribute("title", "Thymeleaf Demo Application");
model.addAttribute("welcomeMessage", "Welcome to Thymeleaf!");
model.addAttribute("currentTime", LocalDateTime.now());
// 사용자 데이터
User currentUser = new User(1L, "John Doe", "john@example.com", "admin");
currentUser.setPermissions(Arrays.asList("READ", "WRITE", "DELETE"));
model.addAttribute("user", currentUser);
// 사용자 목록
List<User> users = Arrays.asList(
new User(1L, "John Doe", "john@example.com", "admin"),
new User(2L, "Jane Smith", "jane@example.com", "user"),
new User(3L, "Bob Johnson", "bob@example.com", "moderator")
);
model.addAttribute("users", users);
return "index";
}
@GetMapping("/user/{id}")
public String userProfile(@PathVariable Long id, Model model) {
// 실제로는 서비스에서 사용자 정보를 조회
User user = new User(id, "John Doe", "john@example.com", "admin");
user.setLastLogin(LocalDateTime.now().minusDays(1));
model.addAttribute("user", user);
model.addAttribute("title", "User Profile - " + user.getName());
return "user/profile";
}
@GetMapping("/search")
public String search(@RequestParam(required = false) String query, Model model) {
model.addAttribute("title", "Search Results");
model.addAttribute("query", query);
if (query != null && !query.trim().isEmpty()) {
// 검색 로직 (예시)
List<User> searchResults = Arrays.asList(
new User(1L, "John Doe", "john@example.com", "admin")
);
model.addAttribute("results", searchResults);
model.addAttribute("resultCount", searchResults.size());
}
return "search";
}
}
메인 템플릿
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="${title}">Thymeleaf Demo</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>
<!-- 헤더 -->
<header class="header">
<nav class="nav">
<h1 th:text="${title}">Thymeleaf Demo Application</h1>
<div class="nav-links">
<a th:href="@{/}">Home</a>
<a th:href="@{/search}">Search</a>
<span th:if="${user}" class="user-info">
Welcome, <span th:text="${user.displayName}">User</span>!
<a th:href="@{/user/{id}(id=${user.id})}">Profile</a>
</span>
</div>
</nav>
</header>
<!-- 메인 콘텐츠 -->
<main class="main-content">
<div class="welcome-section">
<h2 th:text="${welcomeMessage}">Welcome Message</h2>
<p>Current time: <span th:text="${#temporals.format(currentTime, 'yyyy-MM-dd HH:mm:ss')}">2024-01-01 12:00:00</span></p>
</div>
<!-- 현재 사용자 정보 -->
<section th:if="${user}" class="user-section">
<h3>Current User Information</h3>
<div class="user-card">
<h4 th:text="${user.name}">John Doe</h4>
<p>Email: <span th:text="${user.email}">john@example.com</span></p>
<p>Role: <span th:text="${user.role}" th:class="|role-${user.role}|">admin</span></p>
<p>Status:
<span th:text="${user.active} ? 'Active' : 'Inactive'"
th:class="${user.active} ? 'status-active' : 'status-inactive'">Active</span>
</p>
<!-- 권한 목록 -->
<div th:if="${user.permissions}" class="permissions">
<h5>Permissions:</h5>
<ul>
<li th:each="permission : ${user.permissions}"
th:text="${permission}">READ</li>
</ul>
</div>
</div>
</section>
<!-- 사용자 목록 -->
<section th:if="${users}" class="users-section">
<h3>All Users (<span th:text="${#lists.size(users)}">0</span>)</h3>
<div class="users-grid">
<div th:each="u, stat : ${users}"
class="user-card"
th:classappend="${stat.odd} ? 'odd' : 'even'">
<div class="user-header">
<h4 th:text="${u.name}">User Name</h4>
<span th:if="${u.admin}" class="admin-badge">Admin</span>
</div>
<p th:text="${u.email}">user@example.com</p>
<div class="user-actions">
<a th:href="@{/user/{id}(id=${u.id})}" class="btn btn-primary">View Profile</a>
<span th:switch="${u.role}">
<button th:case="'admin'" class="btn btn-danger">Admin Actions</button>
<button th:case="'moderator'" class="btn btn-warning">Mod Actions</button>
<button th:case="*" class="btn btn-secondary">User Actions</button>
</span>
</div>
</div>
</div>
</section>
</main>
<!-- 푸터 -->
<footer class="footer">
<p>© 2024 Thymeleaf Demo. Built with Spring Boot and Thymeleaf.</p>
</footer>
<script th:src="@{/js/app.js}"></script>
</body>
</html>
CSS 스타일
/* src/main/resources/static/css/style.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f8f9fa;
}
.header {
background-color: #007bff;
color: white;
padding: 1rem 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.nav {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav h1 {
font-size: 1.5rem;
font-weight: 300;
}
.nav-links {
display: flex;
gap: 1rem;
align-items: center;
}
.nav-links a {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.nav-links a:hover {
background-color: rgba(255,255,255,0.1);
}
.main-content {
max-width: 1200px;
margin: 2rem auto;
padding: 0 2rem;
}
.welcome-section {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 2rem;
}
.user-section, .users-section {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 2rem;
}
.user-card {
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
background: #f8f9fa;
}
.users-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.user-card.odd {
background-color: #f1f3f4;
}
.user-card.even {
background-color: #ffffff;
}
.user-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.admin-badge {
background-color: #dc3545;
color: white;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: bold;
}
.role-admin { color: #dc3545; font-weight: bold; }
.role-moderator { color: #fd7e14; font-weight: bold; }
.role-user { color: #6c757d; }
.status-active { color: #28a745; font-weight: bold; }
.status-inactive { color: #dc3545; font-weight: bold; }
.permissions ul {
list-style: none;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.permissions li {
background-color: #e9ecef;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
}
.btn {
display: inline-block;
padding: 0.5rem 1rem;
text-decoration: none;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 0.9rem;
transition: background-color 0.2s;
}
.btn-primary { background-color: #007bff; color: white; }
.btn-danger { background-color: #dc3545; color: white; }
.btn-warning { background-color: #ffc107; color: black; }
.btn-secondary { background-color: #6c757d; color: white; }
.btn:hover {
opacity: 0.8;
}
.user-actions {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
}
.footer {
background-color: #343a40;
color: white;
text-align: center;
padding: 1rem 0;
margin-top: 2rem;
}
JavaScript 파일
// src/main/resources/static/js/app.js
document.addEventListener('DOMContentLoaded', function() {
console.log('Thymeleaf Demo App loaded');
// 사용자 카드 클릭 이벤트
const userCards = document.querySelectorAll('.user-card');
userCards.forEach(card => {
card.addEventListener('click', function(e) {
if (!e.target.matches('a, button')) {
this.classList.toggle('expanded');
}
});
});
// 현재 시간 업데이트
updateCurrentTime();
setInterval(updateCurrentTime, 1000);
});
function updateCurrentTime() {
const timeElement = document.querySelector('.current-time');
if (timeElement) {
timeElement.textContent = new Date().toLocaleString();
}
}
애플리케이션 실행
# Maven 사용시
mvn spring-boot:run
# Gradle 사용시
./gradlew bootRun
# JAR 실행
java -jar target/demo-1.0-SNAPSHOT.jar
실행 후 확인사항:
- 브라우저에서
http://localhost:8080
접속 - 사용자 정보가 정상적으로 표시되는지 확인
- 템플릿 수정 후 자동 리로드 작동 확인
- DevTools를 통한 핫 리로드 테스트
이제 기본적인 Thymeleaf 애플리케이션이 완성되었습니다. 다음 장에서는 Thymeleaf의 문법과 표현식에 대해 자세히 알아보겠습니다.
반응형