카테고리 없음

Thymeleaf 가이드 - #2. 개발환경설정

shaprimanDev 2025. 8. 25. 11:29
반응형

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 설정

플러그인 설치

  1. File → Settings → Plugins
  2. Thymeleaf 플러그인 검색 후 설치
  3. 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 설치

  1. Help → Eclipse Marketplace
  2. Spring Tools 4 검색 후 설치
  3. 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>&copy; 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

실행 후 확인사항:

  1. 브라우저에서 http://localhost:8080 접속
  2. 사용자 정보가 정상적으로 표시되는지 확인
  3. 템플릿 수정 후 자동 리로드 작동 확인
  4. DevTools를 통한 핫 리로드 테스트

이제 기본적인 Thymeleaf 애플리케이션이 완성되었습니다. 다음 장에서는 Thymeleaf의 문법과 표현식에 대해 자세히 알아보겠습니다.

반응형