타임리프.properties
#Thymeleaf 환경설정 : .jsp .html .htm => Spring-boot
#JSTL => Thymeleaf
#템플렛 캐쉬를 비활성화 함 : 소스 수정시 새로고침을 자동으로 하게 해서 변경된 소스가 반영됨
spring.thymeleaf.cache=false
#템플렛 View를 이용하여 resource에 디렉토리를 활성화함
spring.thymeleaf.check-template-location=true
#Thymeleaf 기본적으로 사용함
spring.thymeleaf.enabled=true
#src/main/resources => templates 디렉토리를 활성화함
spring.thymeleaf.prefix=classpath:/templates/
#View 파일 속성명 (html과 jsp 속성 파일을 함께 사용시 상위 속성을 입력해야함)
#jsp가 html보다 상위라서 아랫줄에 html쓰면 jsp사용 불가능 html보다 하위인 애들만 쓸 수 있게 됨
spring.thymeleaf.suffix=.jsp
#View 파일을 Controller에서 로드할 수 있도록 함
spring.thymeleaf.mode=html
#view-names : Thymeleaf와 JSTL, JSP를 구분하여 사용하겠다는 뜻
spring.thymeleaf.view-names=/*
#윗 줄 세팅안하면 기존의
#spring.mvc.view.prefix=/
#spring.mvc.view.suffix=.jsp
#이것들과 충돌이 날 수 있음
#spring.thymeleaf.view-names=WEB-INF/views/*
#이렇게 안해도 되는 이유
#spring.thymeleaf.prefix=classpath:/templates/
#이 줄로 이미 보안을 걸었음
all_DTO.java
package kr.co.koo.thymeleaf;
import java.util.ArrayList;
import org.springframework.stereotype.Repository;
import lombok.Data;
//Database와 관계없이 AOP와 Controller에서 서로 상호작용을 하기 위한 DTO
@Data
@Repository("all_DTO")
public class all_DTO {
ArrayList<String> menus;
}
index.html (템플릿)
<!DOCTYPE html>
<html lang="ko" xmlns:th="https://www.thymeleaf.org">
<head th:fragment="title">
<meta charset="UTF-8">
<meta http-equiv="X-UA-compatible" content="IE=Edge,chrome=1">
<meta name="viewport" content="width=device, initial-scale=1.0">
<title>쿠팡 쇼핑몰 리뉴얼</title>
<link
href="https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&display=swap"
rel="stylesheet">
<!--Regular 400-->
<link
href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700&family=Noto+Sans+KR:wght@100;300;400;900&display=swap"
rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./index.css?v=7">
<link rel="stylesheet" type="text/css" href="./top.css?v=6">
<link rel="stylesheet" type="text/css" href="./menu.css?v=6">
<link rel="stylesheet" type="text/css" href="./banner.css?v=3">
<link rel="stylesheet" type="text/css" href="./product.css?v=4">
<link rel="stylesheet" type="text/css" href="./bottom.css?v=2">
<script src="./js/jq.js"></script>
</head>
<body>
<!--이벤트 배너-->
<header class="header_css" th:fragment="top">
<script>
$(document).ready(function(){
$("#close").click(function(){
$("#top1").slideUp(1000);
});
$("#f").submit(function(){
var $search = $("#search").val();
if($search==""){
alert("검색할 상품명을 입력해 주시길 바랍니다.");
return false;
}
else{
$("#f").submit();
}
});
$("#menu_cate>li").eq(0).click(function(){
$("#menu_cate>li:eq(0)>ol").slideDown();
});
$("#banner_outline").mouseover(function(){
$("#banner_outline > span").stop().fadeIn();
});
$("#banner_outline").mouseout(function(){
$("#banner_outline > span").stop().fadeOut();
});
});
</script>
<div class="event_top">
<!--이벤트 배너-->
<label class="top1" id="top1">
<ul class="top_banner">
<li><img src="./image/topbanner1.jpg"></li>
<li><img src="./image/topbanner2.jpg"></li>
<li><span class="close" id="close">X</span></li>
</ul>
</label>
<!--탑 메뉴 : 즐겨찾기~고객센터-->
<label class="top2">
<ul class="ul_left">
<li>즐겨찾기</li>
<li>입장신청▼</li>
</ul>
<ul class="ul_right">
<li onclick="location.href='./shop_login.do';">로그인</li>
<li onclick="location.href='./shop_join.do';">회원가입</li>
<li onclick="location.href='./shop_custom.do';">고객센터</li>
</ul>
</label>
</div>
</header>
<!--로그 & 메뉴-->
<!-- menulist 매개변수값은 main.html, login.html에서 Controller가 적용한 값을 가져오는 방식 -->
<nav class="nav_css" th:fragment="menu(menulist)">
<div class="menu_div">
<label class="menu_logo"> <img src="./image/logo.png">
</label>
<!--
폼 빼고 main.html에서 장착해야함
<form id="f" method="GET" action="https://coupang.com/np/search">
-->
<label class="menu_search"> <input type="text" name="q"
id="search" placeholder="검색할 상품명을 입력해 주시길 바랍니다"> <input
type="submit" value="상품검색">
</label>
<!--
</form>
-->
<label class="menu_part">
<ul id="menu_cate" >
<!-- thymeleaf의 반복문은 each 문법으로 데이터값을 해당 태그에 맞춰서 반복출력 -->
<li th:each="mu : ${menulist}" th:text=${mu}></li>
</ul>
</label>
</div>
</nav>
<!--배너-->
<section class="banner_css" th:fragment="banner">
<div class="banner_outline" id="banner_outline">
<ul class="banner_view">
<li><img src="./banner/banner.png"></li>
<li><img src="./banner/banner.png"></li>
</ul>
<span class="leftcss"><img src="./banner/leftbtn.png"></span> <span
class="rightcss"><img src="./banner/rightbtn.png"></span> <label
class="disc">
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</label>
</div>
</section>
<!--상품출력-->
<article class="article_css" th:fragment="product">
<div class="product">
<p class="product_title">BEST PRODUCT</p>
<ul class="product_ul">
<li>
<div class="product_view">
<span><img src="./product/bestproduct1.png"></span> <span>베트남
쌀국수 3종 세트</span> <span><font color="red">균일가</font> 5,500원</span>
</div>
</li>
<li>
<div class="product_view">
<span><img src="./product/bestproduct2.png"></span> <span>순수
PURE 30EA 50% 세일</span> <span><font color="red">균일가</font>
12,000원</span>
</div>
</li>
<li>
<div class="product_view">
<span><img src="./product/bestproduct3.png"></span> <span>정관장
활기력 20EA 1BOX</span> <span><font color="red">균일가</font> 38,000원</span> <label
class="coupon"><img src="./product/coupon.png"></label>
</div>
</li>
<li>
<div class="product_view">
<span><img src="./product/bestproduct4.png"></span> <span>일렉트로닉
무선 청소기 15% 할인</span> <span><font color="red">균일가</font> 370,000원</span>
<label class="coupon"><img src="./product/coupon.png"></label>
</div>
</li>
</ul>
<p class="product_title">NEW PRODUCT</p>
<ol>
</ol>
</div>
</article>
<!--카피라이터-->
<footer class="footer_css" th:fragment="footer">
<div class="copyright">
<span class="cp1">
<ul>
<li>회사소개</li>
<li>인재채용</li>
<li>입점/제휴문의</li>
<li>공지사항</li>
<li>고객의소리</li>
<li>이용약관</li>
<li>개인정보처리방침</li>
<li>제휴마케팅</li>
<li>광고안내</li>
<li>신뢰관리센터</li>
</ul>
</span> <span class="cp2"> <label>Copyright ⓒ Coupang Corp.
2024-2025 All Rights Reserved.</label>
</span>
</div>
</footer>
</body>
</html>
main.html (템플릿 사용)
<!DOCTYPE html>
<!-- Thymeleaf는 무조건 마크업 기준으로 fragment를 설정하여 가져옴 -->
<html lang="ko" xmlns:th="https://www.thymeleaf.org">
<head th:replace="~{/index.html :: title}"></head>
<body>
<header th:replace="~{/index.html :: top}"></header>
<form id="frm" method="get" action="./search_shop.do" onsubmit="return checks()">
<!-- ${menulist} : Controller에서 Model로 보낸 값을 받아서 index.html에 매개변수 값을 전송 -->
<nav th:replace="~{/index.html :: menu(${menulist})}"></nav>
</form>
<section th:replace="~{/index.html :: banner}"></section>
<article th:replace="~{/index.html :: product}"></article>
<footer th:replace="~{/index.html :: footer}"></footer>
</body>
<script>
function checks(){
if(frm.q.value==""){
alert("검색어를 입력하세요");
return false;
}else{
frm.submit();
}
}
</script>
</html>
login.html (템플릿 사용)
<!DOCTYPE html>
<!-- Thymeleaf는 무조건 마크업 기준으로 fragment를 설정하여 가져옴 -->
<html lang="ko" xmlns:th="https://www.thymeleaf.org">
<head th:replace="~{/index.html :: title}"></head>
<link rel="stylesheet" type="text/css" href="./login.css">
<body>
<header th:replace="~{/index.html :: top}"></header>
<form id="frm" method="get" action="./search_shop.do"
onsubmit="return checks()">
<nav th:replace="~{/index.html :: menu(${menulist})}"></nav>
</form>
<main>
<div class="loginview">
<span class="member_l">MEMBER LOGIN</span> <span class="login">
<input type="hidden" name="nomember_security" value="">
<table border="0" cellpadding="0" cellspacing="0"
class="table_login">
<tr>
<td><input type="text" placeholder="아이디를 입력해주세요" class="id"
name="shop_id"></td>
<td rowspan="2"><input type="submit" value="LOGIN"
class="btn1"></td>
</tr>
<tr>
<td><input type="password" placeholder="패스워드를 입력해주세요"
class="id" name="shop_pw"></td>
</tr>
</table>
<div class="check1">
<input type="checkbox" name="shop_idsave" id="l" value="Y">
<label for="l" class="label_login">아이디 저장</label>
</div>
<ul class="btns_login">
<li><input type="button" value="회원가입" class="a"
onclick="gopage(3)"></li>
<li><input type="button" value="아이디 찾기" class="a"
onclick="alert('서비스 준비중 입니다.');"></li>
<li><input type="button" value="비밀번호 찾기" class="a1"
onclick="alert('서비스 준비중 입니다.');"></li>
</ul> <input type="hidden" name="nomember_security" value="noinfo">
<span class="clear">비회원 주문조회</span> <span class="search_login">
<table border="0" cellpadding="0" cellspacing="0"
class="table_login">
<tr>
<td><input type="text" placeholder="주문자명을 입력해주세요" class="id"
name="nomember_id" tabindex="1"></td>
<td rowspan="2"><input type="submit" value="주문조회"
class="btn1"></td>
</tr>
<tr>
<td><input type="text" placeholder="주문번호를 8자리 입력해 주세요"
class="id" name="nomember_no" maxlength="8" tabindex="2">
</td>
</tr>
</table> <label class="check1"> 주문자명과 주문번호를 잊으신 경우, 고객센터로 문의하여 주시기
바랍니다. </label>
</span>
</span>
</div>
</main>
<footer th:replace="~{/index.html :: footer}"></footer>
</body>
<script>
function checks(){
if(frm.q.value==""){
alert("검색어를 입력하세요");
return false;
}else{
frm.submit();
}
}
</script>
</html>
컨트롤러.java
package kr.co.koo.thymeleaf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import jakarta.annotation.Resource;
//Thymeleaf + spring-boot => Model 갱장희 중요 (extends, implement, AOP)
@Controller
public class thymeleaf_controller {
Logger log = LoggerFactory.getLogger(this.getClass());
//AOP와 공용으로 사용하는 DTO
@Resource(name="all_DTO")
all_DTO all_inject;
/*
return null : webapp에서 찾음
return "파일명" : webapp에서 파일명.jsp 찾음
return "/파일명" : templates에서 파일명.jsp 찾음
return "/파일명.html" : templates에서 파일명.html 찾음
WEB-INF의 파일을 불러오고싶다면
return "/WEB-INF/views/파일명" 이게 아니라
return "WEB-INF/views/파일명" 이렇게 써야함
*/
//Thymeleaf View에 Model로 생성 후 .html에 값을 전달
@GetMapping("sample.do")
public String sample(Model m) {
String product = "냉장고";
m.addAttribute("product",product);
return "/sample.html";
//Thymeleaf안의 jsp는 <% %>이게 안먹어서 코드가 페이지에 노출됨
//파일명만 jsp인거지 기존 jsp처럼 핸들링 불가능
/*
"sample" : /sample.jsp 를 찾음
"/sample" : 500 error (템플렛 안에 jsp있을 경우 출력)
"sample.html" : sample.html.jsp를 찾음
"/sample.html" : 템플렛 안에 html있을 경우 출력
왜? 프로퍼티스에 그렇게 설정했음
*/
}
@GetMapping("sample2.do")
public String sample2(Model m) {
String menu = "관리자 등록";
String copy = "Copyright 2025 WEB By Design...";
m.addAttribute("menu",menu);
m.addAttribute("copy",copy);
return "/subpage.html";
}
//작업용파일
/*
@GetMapping("indextest.do")
public String indextest(Model m) {
return "/index.html";
}
*/
@GetMapping("shop.do")
public String shop(Model m) { //실제 메인페이지
m.addAttribute("menulist",this.all_inject.getMenus());
return "/main.html";
}
@GetMapping("shop_login.do")
public String shop_login(Model m) {
//해당 객체명으로 DTO에 있는 배열값을 타임리프를 이용해 html로 이관
m.addAttribute("menulist",this.all_inject.getMenus());
return "/login.html";
}
}
aop.java
package kr.co.koo.thymeleaf;
import java.util.ArrayList;
import java.util.Arrays;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import jakarta.annotation.Resource;
@Aspect
@Component
public class thymeleaf_aop {
@Resource(name="all_DTO")
all_DTO all_inject;
//thymeleaf_controller 해당 class에 있는 모든 메소드에 Controller 실행 전 해당 AOP가 먼저 작동
@Before("execution(* kr.co.koo.thymeleaf.thymeleaf_controller.*(..)) && args(model,..)")
public void top_menu(Model model) {
String menus[] = {"카테고리", "로켓배송", "로켓프레시", "2025추석", "로켓직구", "골드박스", "정기배송", "이벤트/쿠폰", "기획전", "여행/티켓"};
ArrayList<String> allmenu = new ArrayList<>(Arrays.asList(menus));
this.all_inject.setMenus(allmenu);
}
}