IT 강좌(IT Lectures)/SpringBoot

14강. 스프링부트 웹 애플리케이션 개발

소울입니다 2024. 7. 27. 08:36
728x90
반응형

 

챕터 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";
    }
}
    

위 예제에서는 여러 필드를 가진 폼 데이터를 처리하고, 유효성 검사를 수행하여 오류를 처리합니다.

 

반응형