๐ŸŒฟSpring/Spring Framework

[Spring Framework - Thymeleaf] Decoupled Logic

Boom's 2024. 1. 21. 19:14
๋ฐ˜์‘ํ˜•

 

์ธํ„ฐ๋„ท ๊ฐ•์˜๋ฅผ ์ˆ˜๊ฐ•ํ•˜๋‹ค๊ฐ€  Thymeleaf์˜ Decoupled Logic ์— ๊ถ๊ธˆํ•˜์—ฌ ์•Œ์•„๋ณด๊ธฐ ์œ„ํ•ด์„œ ์ž‘์„ฑ์„ ํ•˜๋ ค๊ณ  ํ•œ๋‹ค.


Decoupled logic์ด ๋ฌด์—‡์ผ๊นŒ?

  • decoupled (๋ถ„๋ฆฌ๋œ) logic์€ thymeleaf์˜ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์ด๋‹ค. 
  • HTML์™€ XML ํ…œํ”Œ๋ฆฟ์— ๋งˆํฌ์—…๊ณผ ๋กœ์ง์ด ๋“ค์–ด๊ฐ„ ๋ถ€๋ถ„์„ ๋ถ„๋ฆฌ์‹œํ‚ค๋Š” ๊ธฐ๋Šฅ
    • ๋”ฐ๋ผ์„œ ๋””์ž์ด๋„ˆ์™€ ํ˜‘์—…์ด ์‰ฌ์›Œ์ง
  • ์ฃผ์š” ์•„์ด๋””์–ด๋Š” ํ…œํ”Œ๋ฆฟ ๋กœ์ง์ด ๋ณ„๋„์˜ ๋กœ์ง ํŒŒ์ผ(ํŒŒ์ผ์ผ ํ•„์š”๋Š” ์—†์œผ๋ฏ€๋กœ ๋” ์ •ํ™•ํ•˜๊ฒŒ๋Š” ๋กœ์ง resource์— ํ•ด๋‹น)์— ์ •์˜
  • ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด ๋กœ์ง ๋ฆฌ์†Œ์Šค๋Š” ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ๊ณผ ๊ฐ™์€ ์œ„์น˜(์˜ˆ: ํด๋”)์— ์žˆ๋Š” ์ถ”๊ฐ€ ํŒŒ์ผ๋กœ, ์ด๋ฆ„์€ ๊ฐ™์ง€๋งŒ ํ™•์žฅ์ž๊ฐ€ .th.xml์ธ ํŒŒ์ผ
/templates
+->/home.html
+->/home.th.xml

์˜ˆ์ œ

  • ์˜ˆ๋ฅผ ๋“ค์–ด home.html ํŒŒ์ผ์€ ์™„์ „ํžˆ ๋กœ์ง์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ•˜๋‹จ์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ๋ณด์ผ ์ˆ˜ ์žˆ๋‹ค.
<!DOCTYPE html>
<html>
  <body>
    <table id="usersTable">
      <tr>
        <td class="username">Jeremy Grapefruit</td>
        <td class="usertype">Normal User</td>
      </tr>
      <tr>
        <td class="username">Alice Watermelon</td>
        <td class="usertype">Administrator</td>
      </tr>
    </table>
  </body>
</html>
  • Thymeleaf ์ฝ”๋“œ๊ฐ€ ์ „ํ˜€ ์—†์œผ๋ฉฐ, ์ด ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์€ Thymeleaf & ํ…œํ”Œ๋ฆฟ์— ๋Œ€ํ•œ ์ง€์‹์ด ์—†๋Š” ๋””์ž์ด๋„ˆ๊ฐ€ ์ƒ์„ฑ, ํŽธ์ง‘ ๋“ฑ ์ดํ•ด ํ•  ์ˆ˜ ์—†๋Š” ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์ด๋‹ค.
<?xml version="1.0"?>
<thlogic>
  <attr sel="#usersTable" th:remove="all-but-first">
    <attr sel="/tr[0]" th:each="user : ${users}">
      <attr sel="td.username" th:text="${user.name}" />
      <attr sel="td.usertype" th:text="#{|user.type.${user.type}|}" />
    </attr>
  </attr>
</thlogic>
  • ์—ฌ๊ธฐ์„œ๋Š” ๋…ผ๋ฆฌ ๋ธ”๋ก ์•ˆ์— ๋งŽ์€ <attr> ํƒœ๊ทธ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
  • ์ด๋Ÿฌํ•œ <attr> ํƒœ๊ทธ๋Š” Thymeleaf ๋งˆํฌ์—… ์„ ํƒ๊ธฐ(์‹ค์ œ๋กœ๋Š” AttoParser ๋งˆํฌ์—… ์„ ํƒ๊ธฐ)๊ฐ€ ํฌํ•จ๋œ sel ์†์„ฑ์„ ํ†ตํ•ด ์„ ํƒ๋œ ์›๋ณธ ํ…œํ”Œ๋ฆฟ์˜ ๋…ธ๋“œ์— ์†์„ฑ ์ฃผ์ž…์„ ์ˆ˜ํ–‰ํ•จ
  • <attr> ํƒœ๊ทธ๋Š” ์ค‘์ฒฉ๋˜์–ด ์„ ํƒ๊ธฐ๊ฐ€ ์ถ”๊ฐ€๋  ์ˆ˜ ์žˆ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด ์œ„์˜ sel="/tr[0]"์€ sel="#usersTable/tr[0]"๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.
    • ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž ์ด๋ฆ„ <td>์— ๋Œ€ํ•œ ์„ ํƒ๊ธฐ๋Š” sel="#usersTable/tr[0]//td.username"์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.

Spring์—์„œ thymeleaf์˜ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์‚ฌ์ „์ž‘์—…

  • ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์ „์— application.yaml์— ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
thymeleaf3:
	decoupled-locig: true
  • build.gradle์—์„œ ๋””ํŽœ๋˜์‹œ์— spring configuration prosser๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.
    • thymeleaf3์™€ IDE๋ฅผ ๋” ๋งค๋„๋Ÿฝ๊ฒŒ ์—ฐ๋™ํ•˜๋Š” ๋ฐฉ๋ฒ•
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

Spring์—์„œ thymeleaf์˜ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ณตํ†ต Config ํŒŒ์ผ 

  • ์Šคํ”„๋ง ๋ถ€ํŠธ ํ”„๋กœํผํ‹ฐ์—์„œ ์„ค์ • ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๊ณต๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์„ธํŒ…์„ ๋”ฐ๋กœ ์ง„ํ–‰
  • ThymeleafConfig ๋ผ๋Š” Config ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
@Configuration
public class ThymeleafConfig {

    @Bean
    public SpringResourceTemplateResolver thymeleafTemplateResolver(
            SpringResourceTemplateResolver defaultTemplateResolver,
            Thymeleaf3Properties thymeleaf3Properties
    ) {
        defaultTemplateResolver.setUseDecoupledLogic(thymeleaf3Properties.isDecoupledLogic());

        return defaultTemplateResolver;
    }


    @RequiredArgsConstructor
    @Getter
    @ConstructorBinding
    @ConfigurationProperties("spring.thymeleaf3")
    public static class Thymeleaf3Properties {
        /**
         * Use Thymeleaf 3 Decoupled Logic
         */
        private final boolean decoupledLogic;
    }

}

 

 

  • ์œ„ config ํŒŒ์ผ๊นŒ์ง€ ์ƒ์„ฑ ํ•˜๊ฒŒ ๋˜๋ฉด ๊ธฐ์กด์˜ HTML์—์„œ decoupled logic์œผ๋กœ ํ•˜์—ฌ ๋ถ„๋ฆฌํ•ด๋ด์•ผํ•œ๋‹ค.
  • resource > templates > articles์—์„œ index.html ํŒŒ์ผ์— ๋™์ผํ•œ ์ด๋ฆ„์œผ๋กœ index.th.xml ํŒŒ์ผ์„ ์ƒ์„ฑ

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thyemleaf.org">
<head>
    <title>๊ฒŒ์‹œํŒ ํŽ˜์ด์ง€</title>
</head>

<body>

<header id="header"></header>

<main class="container">
  
</main>

<footer id="footer"></footer>

</body>
</html>
  • ์œ„์— index.html์— header ๋ฐ footer๋กœ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•ด์ค˜์•ผ ํ•œ๋‹ค.
<?xml version="1.0"?>
<thlogic>
    <attr sel="#header" th:replace="header :: header"/>
    <attr sel="#footer" th:replace="footer :: footer"/>
</thlogic>
  • ๊ทธ๋ ‡๊ฒŒ ๋™์ผํ•œ name์„ ๊ฐ€์ง„ index.th.xml ํŒŒ์ผ์— html ํŒŒ์ผ์—์„œ ๋ถ„๋ฆฌ ๋œ header์™€ footer๋ฅผ attr ๊ตฌ๋ฌธ์œผ๋กœ ํ•ด์„œ ๊ฐ’์„ ๋„ฃ์–ด์ค€๋‹ค.
  • ๋ถ„๋ฆฌ๋œ header ๊ตฌ๋ฌธ์„ ํด๋ฆญ ํ•˜๊ฒŒ ๋˜๋ฉด, ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž‘์„ฑํ•œ header.html ํŒŒ์ผ๋กœ ์ด๋™ํ•˜๊ฒŒ ๋œ๋‹ค.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<header class="p-3 bg-dark text-white">
    <div class="container">
        <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
            <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                <li><a id="home" href="#" class="nav-link px-2 text-secondary">Home</a></li>
            </ul>

            <div class="text-end">
                <a role="button" class="btn btn-outline-light me-2">Login</a>
                <a role="button" class="btn btn-warning">Sign-up</a>
            </div>
        </div>
    </div>
</header>
</body>
</html>

 

 

  • ์œ„์˜ ์ด๋ฏธ์ง€ ์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํด๋ž˜์Šค ๊ด€๋ฆฌ๊ฐ€ ๋œ๋‹ค.

 

https://github.com/94-c/project-board/commit/1d3bcb50dcecea63f2220fcbf004fdf66110f22e

 

๋ฐ˜์‘ํ˜•