챕터 14: 스프링부트 웹 애플리케이션 개발
14.1 Thymeleaf 템플릿 엔진
14.1.1 고급 템플릿 기능
Thymeleaf는 단순한 데이터 바인딩뿐만 아니라, 조건부 렌더링, 반복, 텍스트 국제화 등 다양한 고급 템플릿 기능을 제공합니다.
역사적 배경:
- 과거에는 JSP(JavaServer Pages)를 많이 사용했으나, 이는 복잡한 설정과 코드 관리의 어려움이 있었습니다.
- Thymeleaf는 이러한 문제를 해결하고자 등장했으며, HTML 템플릿을 보다 직관적으로 작성할 수 있게 해줍니다.
관련된 Java 개념: JSP, Servlets
<!-- 조건부 렌더링 -->
<p th:if="${user != null}" th:text="'Hello, ' + ${user.name}">Hello, User</p>
<!-- 반복 -->
<ul>
<li th:each="item : ${items}" th:text="${item}">Item</li>
</ul>
<!-- 텍스트 국제화 -->
<p th:text="#{welcome.message}">Welcome</p>
<!-- src/main/resources/templates/layout.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:replace="fragments/header :: title">Title</title>
</head>
<body>
<div th:replace="fragments/header :: header">Header</div>
<div th:replace="fragments/menu :: menu">Menu</div>
<div th:replace="fragments/content :: content">Content</div>
<div th:replace="fragments/footer :: footer">Footer</div>
</body>
</html>
14.1.2 템플릿 레이아웃 작성
Thymeleaf는 템플릿 레이아웃을 작성할 수 있는 기능을 제공합니다. 이를 통해 공통 레이아웃을 정의하고, 개별 페이지에서 이를 확장하여 사용할 수 있습니다.
<!-- src/main/resources/templates/layout.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:replace="fragments/header :: title">Title</title>
</head>
<body>
<div th:replace="fragments/header :: header">Header</div>
<div th:replace="fragments/menu :: menu">Menu</div>
<div th:replace="fragments/content :: content">Content</div>
<div th:replace="fragments/footer :: footer">Footer</div>
</body>
</html>
<!-- src/main/resources/templates/fragments/header.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:fragment="title">My Website</title>
</head>
<body>
<div th:fragment="header">
<h1>Welcome to My Website</h1>
</div>
</body>
</html>
14.2 폼 처리와 유효성 검사
14.2.1 폼 데이터 바인딩
Spring Boot와 Thymeleaf를 사용하여 폼 데이터를 바인딩할 수 있습니다. 폼 데이터를 처리하기 위해 모델 객체를 사용합니다.
package com.example.demo;
import javax.validation.constraints.NotEmpty;
public class UserForm {
@NotEmpty(message = "Name is required")
private String name;
// Getter와 Setter 메서드
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
<!-- src/main/resources/templates/form.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Form Example</title>
</head>
<body>
<form th:action="@{/submit}" th:object="${userForm}" method="post">
<label for="name">Name:</label>
<input type="text" id="name" th:field="*{name}">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</div>
<button type="submit">Submit</button>
</form>
</body>
</html>
14.2.2 유효성 검사 및 오류 처리
Spring Boot는 Hibernate Validator를 사용하여 폼 데이터를 검증할 수 있습니다. @Valid 어노테이션을 사용하여 유효성 검사를 수행할 수 있습니다.
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import javax.validation.Valid;
@Controller
public class FormController {
@GetMapping("/form")
public String showForm(Model model) {
model.addAttribute("userForm", new UserForm());
return "form";
}
@PostMapping("/submit")
public String submitForm(@Valid @ModelAttribute UserForm userForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "form";
}
return "success";
}
}
14.3 정적 자원 제공
14.3.1 정적 자원 캐싱
정적 자원(CSS, JS, 이미지 등)은 src/main/resources/static
폴더에 위치합니다. Spring Boot는 기본적으로 이 폴더의 정적 자원을 제공하며, 캐싱을 통해 성능을 향상시킬 수 있습니다.
# application.properties
spring.resources.cache.period=3600
14.3.2 버전 관리
정적 자원의 버전 관리는 캐싱된 자원을 갱신할 때 유용합니다. Spring Boot는 버전 관리를 위한 다양한 방법을 제공합니다.
package com.example.demo;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.VersionResourceResolver;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
}
14.4 파일 업로드 및 다운로드
14.4.1 파일 업로드 설정
Spring Boot를 사용하여 파일 업로드를 설정할 수 있습니다. MultipartFile을 사용하여 파일 데이터를 처리합니다.
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Controller
public class FileUploadController {
private static String UPLOAD_DIR = "uploads/";
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file, RedirectAttributes attributes) {
if (file.isEmpty()) {
attributes.addFlashAttribute("message", "Please select a file to upload.");
return "redirect:/status";
}
try {
// 파일을 업로드 디렉토리에 저장
byte[] bytes = file.getBytes();
Path path = Paths.get(UPLOAD_DIR + file.getOriginalFilename());
Files.write(path, bytes);
attributes.addFlashAttribute("message", "You successfully uploaded '" + file.getOriginalFilename() + "'");
} catch (IOException e) {
e.printStackTrace();
}
return "redirect:/status";
}
}
14.4.2 파일 다운로드 처리
Spring Boot를 사용하여 파일 다운로드를 처리할 수 있습니다. Resource와 HttpServletResponse를 사용하여 파일을 클라이언트에 전송합니다.
package com.example.demo;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.File;
import java.io.IOException;
@Controller
public class FileDownloadController {
private static String DOWNLOAD_DIR = "uploads/";
@GetMapping("/download")
public ResponseEntity downloadFile(@RequestParam String filename) throws IOException {
File file = new File(DOWNLOAD_DIR + filename);
if (!file.exists()) {
return ResponseEntity.notFound().build();
}
Resource resource = new FileSystemResource(file);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"")
.body(resource);
}
}
위 예제에서는 파일 업로드와 다운로드를 설정하고 처리하는 방법을 보여줍니다. 파일을 업로드 디렉토리에 저장하고, 클라이언트 요청 시 해당 파일을 다운로드합니다.
추가 예제 코드
예제 1: 다국어 지원을 위한 메시지 소스 설정
다국어 지원을 위한 메시지 소스를 설정하고 사용하는 예제입니다.
# messages.properties
welcome.message=Welcome to our website!
# messages_ko.properties
welcome.message=우리 웹사이트에 오신 것을 환영합니다!
package com.example.demo;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
@Configuration
public class MessageConfig {
// 메시지 소스를 설정하는 메서드
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
예제 2: 정적 자원의 국제화
정적 자원의 국제화를 설정하는 예제입니다. 정적 자원의 이름에 로케일 정보를 포함시켜 각 언어에 맞는 자원을 제공할 수 있습니다.
package com.example.demo;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new VersionResourceResolver().addFixedVersionStrategy("1.0", "/**"));
}
}
<!-- src/main/resources/templates/index.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Home</title>
<link rel="stylesheet" type="text/css" th:href="@{/css/styles.css}">
</head>
<body>
<p th:text="#{welcome.message}">Welcome</p>
</body>
</html>
예제 3: 복잡한 폼 데이터 바인딩
여러 폼 필드를 가진 복잡한 폼 데이터를 바인딩하는 예제입니다.
package com.example.demo;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
public class ComplexForm {
@NotEmpty(message = "Name is required")
private String name;
@NotNull(message = "Age is required")
private Integer age;
@Size(min = 1, message = "At least one skill is required")
private List skills;
// Getter와 Setter 메서드
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List getSkills() {
return skills;
}
public void setSkills(List skills) {
this.skills = skills;
}
}
<!-- src/main/resources/templates/complexForm.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Complex Form Example</title>
</head>
<body>
<form th:action="@{/submitComplexForm}" th:object="${complexForm}" method="post">
<label for="name">Name:</label>
<input type="text" id="name" th:field="*{name}">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</div>
<label for="age">Age:</label>
<input type="number" id="age" th:field="*{age}">
<div th:if="${#fields.hasErrors('age')}" th:errors="*{age}">Age Error</div>
<label for="skills">Skills:</label>
<input type="text" id="skills" th:field="*{skills}" multiple>
<div th:if="${#fields.hasErrors('skills')}" th:errors="*{skills}">Skills Error</div>
<button type="submit">Submit</button>
</form>
</body>
</html>
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import javax.validation.Valid;
@Controller
public class ComplexFormController {
@GetMapping("/complexForm")
public String showComplexForm(Model model) {
model.addAttribute("complexForm", new ComplexForm());
return "complexForm";
}
@PostMapping("/submitComplexForm")
public String submitComplexForm(@Valid @ModelAttribute ComplexForm complexForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "complexForm";
}
return "success";
}
}
위 예제에서는 여러 필드를 가진 폼 데이터를 처리하고, 유효성 검사를 수행하여 오류를 처리합니다.
'IT 강좌(IT Lectures) > SpringBoot' 카테고리의 다른 글
15강. RESTful 웹 서비스 (0) | 2024.07.28 |
---|---|
13강. 애플리케이션 설정 관리 (0) | 2024.07.26 |
12강. 스프링부트와 국제화(I18N) (0) | 2024.07.25 |
11강. 프론트엔드 통합 (1) | 2024.07.23 |
10강. Spring Boot DevTools (3) | 2024.07.23 |