Spring boot + SpringMVC Web + Thymeleaf(IDEA多模块项目)
Spring Boot干货系列:(四)开发Web应用之Thymeleaf篇
Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎。类似JSP,Velocity,FreeMaker等,它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。与其它模板引擎相比,Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用。它的功能特性如下:
- Spring MVC中@Controller中的方法可以直接返回模板名称,接下来Thymeleaf模板引擎会自动进行渲染
- 模板中的表达式支持Spring表达式语言(Spring EL)
- 表单支持,并兼容Spring MVC的数据绑定与验证机制
- 国际化支持
Spring官方也推荐使用Thymeleaf,所以本篇代码整合就使用Thymeleaf来整合。
引入依赖
引入Thymeleaf的依赖

1 <!--WEB支持--> 2 <dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-web</artifactId> 5 </dependency> 6 <!--thymeleaf--> 7 <dependency> 8 <groupId>org.springframework.boot</groupId> 9 <artifactId>spring-boot-starter-thymeleaf</artifactId> 10 </dependency> 11 <!--thymeleaf layout--> 12 <dependency> 13 <groupId>nz.net.ultraq.thymeleaf</groupId> 14 <artifactId>thymeleaf-layout-dialect</artifactId> 15 </dependency> 16 <!-- 允许使用非严格的 HTML 语法 --> 17 <dependency> 18 <groupId>net.sourceforge.nekohtml</groupId> 19 <artifactId>nekohtml</artifactId> 20 <version>1.9.22</version> 21 </dependency>
设置Thymeleaf属性

1 # thymeleaf 2 spring.thymeleaf.prefix=classpath:/templates/ 3 spring.thymeleaf.check-template-location=true 4 spring.thymeleaf.suffix=.html 5 spring.thymeleaf.encoding=UTF-8 6 spring.thymeleaf.servlet.content-type=text/html 7 # 用非严格的 HTML 8 spring.thymeleaf.mode=HTML 9 # 开发时关闭缓存,不然没法看到实时页面 10 spring.thymeleaf.cache=false
注意:Program.java类文件里面已经没有关于servlet的配置了。

1 package tb.view.sbmsm.easyuidemo; 2 3 import org.mybatis.spring.annotation.MapperScan; 4 import org.springframework.boot.SpringApplication; 5 import org.springframework.boot.autoconfigure.SpringBootApplication; 6 7 @SpringBootApplication(scanBasePackages = {"tb.view.sbmsm.easyuidemo"}) 8 @MapperScan(basePackages = "tb.db.mysql.shopcart.mybatis") 9 public class Program { 10 11 public static void main(String[] args) { 12 SpringApplication.run(Program.class, args); 13 } 14 15 }
Index.html

1 <!DOCTYPE html> 2 <html lang="en" xmlns:th=”http://www.thymeleaf.org“ > 3 <head> 4 <meta charset="UTF-8"> 5 <title th:text="#{WEB_SITE_NAME}"></title> 6 </head> 7 <body> 8 <h1>Hello World</h1> 9 <h2 th:text="${Email}"></h2> 10 <hr/> 11 <h3 th:text="${UserDir}"></h3> 12 </body> 13 </html>
引入依赖后就在默认的模板路径src/main/resources/templates
下编写模板文件即可完成。这里我们新建一个HelloWorld.html:
注:通过xmlns:th=”http://www.thymeleaf.org“ 命令空间,将静态页面转换为动态的视图,需要进行动态处理的元素将使用“th:”前缀。
教程:使用Thymeleaf
Thymeleaf佈局layout

1 <!DOCTYPE html> 2 <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> 3 <head th:fragment="Head(Title, Header)"> 4 <title th:replace="${Title}">EasyUI Demo</title> 5 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> 6 <meta name="viewport" content="width=device-width" /> 7 <meta name="renderer" content="webkit"> 8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> 9 <meta http-equiv="imagetoolbar" content="no"> 10 <link href="/favicon.ico" rel="shortcut icon" type="image/x-icon" /> 11 <link href="/favicon.ico" rel="icon" type="image/x-icon" /> 12 13 <link href="/Content/TB/reset.css" rel="stylesheet" /> 14 <link href="/Content/jquery.easyUI/1.5.1/themes/gray/easyui.css" rel="stylesheet" type="text/css" id="themes" /> 15 <link href="/Content/jquery.easyUI/1.5.1/themes/icon.css" rel="stylesheet" /> 16 <link href="/Content/TB.Behind/form.css" rel="stylesheet" /> 17 <link href="/Content/TB.Behind/fix.css" rel="stylesheet" /> 18 <link href="/Content/TB.Behind/TB.Behind.css" rel="stylesheet" /> 19 20 <script src="/Content/jquery/1.x/jquery-1.8.2.js" language="javascript" type="text/javascript"></script> 21 <script src="/Content/jquery.plugins/jquery.cookie.js" language="javascript" type="text/javascript"></script> 22 <script src="/Content/jquery.plugins/jquery.media.js" language="javascript" type="text/javascript"></script> 23 24 <script src="/Content/jquery.easyUI/1.5.1/jquery.easyui.min.js" language="javascript" type="text/javascript"></script> 25 <script src="/Content/jquery.easyUI/1.5.1/locale/easyui-lang-zh_CN.js" language="javascript" type="text/javascript"></script> 26 <script src="/Content/jquery.easyUI/1.5.1/jquery.easyui.extensions.js" language="javascript" type="text/javascript"></script> 27 28 <script src="/Content/TB/TB.js" language="javascript" type="text/javascript"></script> 29 <script src="/Content/TB.Behind/TB.Behind.js" language="javascript" type="text/javascript"></script> 30 <script src="/Content/TB/fix.js" language="javascript" type="text/javascript"></script> 31 32 <th:block th:replace="${Header}" /> 33 </head> 34 35 <!--BODY START--> 36 <th:block layout:fragment="Body" /> 37 <!--BODY END--> 38 <!--Footer START--> 39 <th:block layout:fragment="Scripts"/> 40 <!--Footer END--> 41 42 </html>

1 <!DOCTYPE html> 2 <html xmlns:th="http://www.thymeleaf.org" 3 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> 4 <head th:replace="Shared/_Layout :: Head(~{this :: title}, ~{this :: .custom-Header})" > 5 <title th:text="${WEB_SITE_NAME}"></title> 6 <!--脚本引入区域--> 7 <style class="custom-Header"> 8 html, body { 9 height: 100%; 10 overflow: hidden; 11 } 12 </style> 13 <script class="custom-Header" src="/Content/TB/TB.Hock.js"></script> 14 <script class="custom-Header" src="/Content/jquery.easyUI/extend/tree_search.js" language="javascript" type="text/javascript"></script> 15 </head> 16 <body class="easyui-layout"> 17 <th:block layout:fragment="Body"> 18 <!--左边菜单--> 19 <div data-options="region:'west',title:'菜单目录',split:true" style="width: 200px;position:relative;"> 20 <input type="text" id="search" autocomplete="off" data-options="prompt: '菜单搜索'" style="height: 20px; width: 97%; ime-mode: active" class="easyui-validatebox" /> 21 <ul id="leftMenu"></ul> 22 </div> 23 <div data-options="region:'center',split:true"> 24 <div id="tab" class="easyui-tabs"> 25 <div data-options="title:'首页',iconCls:'icon-home',closable:false,iniframe:true,href:'/Home/Welcome/',fit:true" style="overflow: hidden;"> 26 </div> 27 </div> 28 </div> 29 <!--底--> 30 <div data-options="region:'south',title:'',split:false" style="height: 33px; overflow: hidden;"> 31 <table style="width: 100%;"> 32 <tr> 33 <td style="width: 200px; text-align: left;"><span style="line-height: 25px;">版本:@Html.Raw(ViewBag.AssemblyVersion)</span></td> 34 <td style="width: auto; text-align: center;"><span style="line-height: 25px;">版权:北京踏浪者科技有限公司</span></td> 35 <td style="width: 306px; text-align: left;"> 36 <table style="width: 100%;"> 37 <tr> 38 <td> 39 <a class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-logout'" href="/Auth/Logout/" id="exit" name="exit" title="注销登陆"></a> 40 <a class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-edit'" id="updatePwd" name="updatePwd" title="修改密码"></a> 41 <a class="easyui-linkbutton" data-options="plain:true,iconCls:'icon-zoom'" id="btnFullScreen" name="btnFullScreen" title="全屏模式切换"></a> 42 </td> 43 <td> 44 <div class="datagrid-btn-separator"></div> 45 <span style="line-height: 25px;">页面主题:</span><select id="themeSelector"></select> 46 </td> 47 </tr> 48 </table> 49 </td> 50 </tr> 51 </table> 52 </div> 53 </th:block> 54 55 <th:block layout:fragment="Scripts"> 56 <script type="text/javascript"> 57 $("#themeSelector").combobox({ 58 width: 140, editable: false, data: $.easyui.theme.themes, 59 value: $.easyui.theme.get(), 60 onSelect: function (record) { 61 $.easyui.theme.change(record.value); 62 } 63 }); 64 window.tabPages = $('#tab').tabs({ 65 border: false, 66 fit: true, 67 enableConextMenu: true, 68 lineHeight: 0 69 }); 70 //TB.hock.tabs.dragTab('#tab'); 71 72 // /Home/GetMenuData 73 $("#leftMenu").tree({ 74 method: "post", 75 lines: true, 76 url: "", 77 smooth: true, 78 checkbox: false, 79 toggleOnClick: true, 80 onClick: function (node) { 81 TB.tabShow(node.text, node.attributes, node.iconCls); 82 }, 83 onCollapse: function (node) { 84 if (node.id == "00000000-0000-0000-0000-000000000000") { 85 $(this).tree("collapseAll"); 86 } 87 }, 88 //onLoadSuccess: function (node) { 89 // $(this).tree("collapseAll"); 90 // $('#left_menu').find('[node-id=00000000-0000-0000-0000-000000000000]').click(); 91 //} 92 }); 93 94 $("#btnFullScreen").click(function () { 95 if (TB.fullScreen.supports) { 96 if (TB.fullScreen.isFullScreen()) { 97 TB.fullScreen.cancel(); 98 } else { 99 TB.fullScreen.request(); 100 } 101 } else { 102 TB.error("当前浏览器不支持全屏 API,请更换至最新的 Chrome/Firefox/Safari 浏览器或通过 F11 快捷键进行操作。"); 103 } 104 }); 105 //修改密码 106 $('#updatePwd').on("click", function () { 107 TB.dialog({ 108 title: "修改密码", 109 width: 400, 110 height: 300, 111 href: "/Auth/ChangePassword", 112 iframe: true, 113 buttons: [{ 114 text: '确定', 115 iconCls: 'icon-ok', 116 handler: function (dialog, iframe) { 117 top.window.location.replace("/Auth/Logout/"); 118 } 119 }] 120 }); 121 }); 122 $(function () { 123 $("#search").on("keyup",function() { 124 $("#leftMenu").tree("search", $(this).val()); 125 if ($(this).val() === "" || null == $(this).val()) { 126 $('#left_menu').tree("expandAll"); 127 } 128 return true; 129 }); 130 }); 131 </script> 132 </th:block> 133 </body> 134 135 </html>

1 <!DOCTYPE html> 2 <html xmlns:th="http://www.thymeleaf.org" 3 xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> 4 <head th:replace="Shared/_Layout :: Head(~{this :: title}, ~{this :: .custom-Header})" > 5 <title></title> 6 <style class="custom-Header"> 7 * { 8 font-size: 12px; 9 padding: 0; 10 margin: 0; 11 } 12 13 .panel-body { 14 overflow: initial; 15 padding: 5px; 16 } 17 18 .panel { 19 margin-bottom: 5px; 20 } 21 22 .ver { 23 width: 100%; 24 overflow: hidden; 25 } 26 27 .ver li { 28 display: block; 29 position: relative; 30 width: 100%; 31 padding: 10px 0; 32 } 33 34 .ver li:before { 35 display: block; 36 position: absolute; 37 left: 80px; 38 width: 2px; 39 height: 100%; 40 background-color: #ccc; 41 content: ''; 42 } 43 44 .ver li:after { 45 display: block; 46 position: absolute; 47 top: 26px; 48 left: 74px; 49 width: 15px; 50 height: 15px; 51 border-radius: 50%; 52 background-color: #999; 53 content: ''; 54 } 55 56 .ver .ver-left { 57 display: block; 58 position: absolute; 59 width: 70px; 60 padding-top: 13px; 61 padding-right: 10px; 62 font-size: 14px; 63 text-align: right; 64 color: #999; 65 } 66 67 .ver .ver-right { 68 display: block; 69 position: relative; 70 margin-left: 100px; 71 padding: 8px; 72 border-radius: 3px; 73 border: solid 1px #ccc; 74 background-color: #FaFaFa; 75 } 76 77 .ver .ver-right:after { 78 display: block; 79 position: absolute; 80 top: 16px; 81 left: -6px; 82 width: 0; 83 height: 0; 84 content: ''; 85 border-top: 6px solid transparent; 86 border-right: 6px solid #ccc; 87 border-bottom: 6px solid transparent; 88 } 89 90 .ver .ver-title { 91 padding: 0 5px 5px 5px; 92 font-size: 14px; 93 border-bottom: 1px solid #ccc; 94 } 95 96 .ver .ver-title code { 97 font-weight: bold; 98 font-style: normal; 99 } 100 101 .ver .ver-content { 102 display: block; 103 padding: 5px 0; 104 line-height: 20px; 105 } 106 /*.ver li:first-child:after { background-color: #000;}*/ 107 </style> 108 <link class="custom-Header" href="/Content/TB/TB.Task.css" rel="stylesheet" /> 109 </head> 110 <body> 111 <th:block layout:fragment="Body"> 112 <div style="padding: 5px;"> 113 <div class="easyui-panel" data-options="collapsible:true,fit:false" style="width:100%"> 114 <div style="padding: 5px;">尊敬的<b> @Model </b>,您好!欢迎使用踏浪者DEMO系统。</div> 115 </div> 116 <div class="easyui-panel" title="SnowFlake全局唯一ID及工具类" data-options="collapsible:true,fit:false" style="width:100%"> 117 <a href="https://www.cnblogs.com/relucent/p/4955340.html">雪花构建全局唯一ID:</a><b>@ViewBag.GlobalID</b> 118 </div> 119 <div class="easyui-panel" title="扫码枪Demo" data-options="collapsible:true,fit:false" style="width:100%"> 120 <div data-options="border:false" style="overflow: hidden; background-color: #f0f0f0;"> 121 <div style="height: 80px; overflow: hidden;"> 122 <input id="input" class="scanbox" placeholder="请扫描包裹号" /> 123 </div> 124 </div> 125 </div> 126 <div class="easyui-panel" title="账户信息" data-options="collapsible:true,fit:false" style="width:100%"> 127 <div id="user-info"> 128 正在拉取数据... 129 </div> 130 <script type="text/template" id="user-info-template"> 131 <ul> 132 <li>登录账户:{{ LoginAccount}}</li> 133 <li>登录IP:{{ LoginIP}}</li> 134 <li>联系电话:{{ Phone}}</li> 135 <li>账户状态:{{ Status}}</li> 136 <li>所属权限组:{{ GroupText}}</li> 137 <li>所属角色组:{{ RoleText}}</li> 138 </ul> 139 </script> 140 </div> 141 <div class="easyui-panel" title="更新内容(最近5次)" data-options="collapsible:true,fit:false" style="width:100%"> 142 <div id="ver"> 143 正在拉取数据... 144 </div> 145 <script type="text/template" id="ver-template-item"> 146 {{each list as item}} 147 <li> 148 <div class="ver-left">{{ item.Time}}</div> 149 <div class="ver-right"> 150 <div class="ver-title"> 151 <code>v{{ item.Version}}</code> {{ item.Title}} {{# item.IsNew==true?"<img src='/Content/Images/new.gif' alt='new' style='vertical-align: middle;' />":"" }} 152 </div> 153 <div class="ver-content">{{# item.Content}}</div> 154 </div> 155 </li> 156 {{/each}} 157 <li> 158 <div class="ver-left">更早</div> 159 <div class="ver-right"> 160 <div class="ver-content"><a href="javascript:void(0);" onclick="TB.tabShow('更新记录', '/Basis/Version/VersionView');">查看更多</a></div> 161 </div> 162 </li> 163 164 </script> 165 </div> 166 <div class="easyui-panel" title="EasyUI官网" data-options="collapsible:true,fit:false" style="width:100%"> 167 <a href="http://www.jeasyui.com/" target="_blank">http://www.jeasyui.com/</a> 168 </div> 169 <div class="easyui-panel" title="常用操作" data-options="collapsible:true,fit:false" style="width:100%"> 170 <div id="ver"> 171 <button class="easyui-linkbutton" id="dig-alert">alert</button> 172 <button class="easyui-linkbutton" id="dig-warn">warn</button> 173 <button class="easyui-linkbutton" id="dig-info">info</button> 174 <button class="easyui-linkbutton" id="di-show">show</button> 175 <button class="easyui-linkbutton" id="di-dialog">dialog</button> 176 <button class="easyui-linkbutton" id="di-confirm">confirm</button> 177 <button class="easyui-linkbutton" id="di-wait">wait</button> 178 <button class="easyui-linkbutton" id="di-open">open</button> 179 <button class="easyui-linkbutton" id="di-api">api</button> 180 <button class="easyui-linkbutton" id="di-task">task</button> 181 <button class="easyui-linkbutton" id="di-notity">notity</button> 182 </div> 183 </div> 184 </div> 185 <a class="media"></a> 186 </th:block> 187 <th:block layout:fragment="Scripts"> 188 <script src="/Content/jquery.plugins/jquery.media.js"></script> 189 <script src="/Content/TB/TB.scanbox.js"></script> 190 <script src="/Content/TB/TB.Task.min.js"></script> 191 <script src="/Content/TB/TB.Notity.min.js"></script> 192 <script type="text/javascript"> 193 $('[id^=dig]').on('click', function () { 194 //TB.alert(); 195 //TB.warn(); 196 // 197 TB[$(this).text()]("测试消息"); 198 }); 199 $('#di-show').on('click', function () { 200 //TB.show(content, title, icon, position, timeout); 201 TB.show("测试消息", "xxxx"); 202 }); 203 $('#di-dialog').on('click', function () { 204 TB.dialog({ 205 title: "添加站点", 206 width: 500, 207 height: 420, 208 modal: true, 209 href: "http://www.baidu.com", 210 iframe: true, 211 buttons: [{ 212 text: '确定', 213 iconCls: 'icon-ok', 214 handler: function (dialog, iframe) { 215 //iframe.contentWindow.save(function () { 216 // cache.view.datagrid('reload'); 217 // dialog.dialog('close'); 218 //}); 219 220 TB.alert("确定"); 221 } 222 }] 223 }); 224 }); 225 $('#di-confirm').on('click', function () { 226 TB.confirm("测试消息", function (r) { 227 228 //r==true/false 229 }); 230 }); 231 $('#di-wait').on('click', function () { 232 TB.wait("测试消息"); 233 setTimeout(function () { 234 TB.wait(null); 235 }, 5000); 236 }); 237 $('#di-open').on('click', function () { 238 TB.open("http://www.baidu.com", "_blank", { s: "tidebuy" }); 239 }); 240 $('#di-api').on('click', function () { 241 TB.api("/Home/Api", { s: "tidebuy" }, function (r) { 242 243 TB.alert("这个回调可以刷新数据"); 244 }); 245 }); 246 $('#di-task').on('click', function () { 247 248 //TB.task更多参数见\Content\TB\TB.Task.readme 249 TB.task({ 250 args: ["a", "b", "c", 'd', 'e', 'f'], 251 onRun: function (data, index) { 252 return $.ajax({ 253 url: index == 1 ? "http://a.com" : "/home/Search?keyword=" + data + "&callback=?" 254 }); 255 }, 256 onSuccess: function (state) { console.log("成功:" + state.success + "个!"); }, 257 onError: function (state) { console.log("失败:" + state.error + "个!"); }, 258 onComplete: function (state) { console.log("完成,成功:" + state.success + "个,失败:" + state.error + "个!"); }, 259 onProgress: function (state) { console.log("进度:" + state.progress + "/" + state.total); }, 260 interval: 1000, 261 dialog: { 262 formatter: function (state) { 263 return state.progress + ". 执行任务" + state.index + " [搜索“" + state.arg + "”]:" + (state.data ? state.data.Message : "失败!"); 264 } 265 } 266 }).run(); 267 }); 268 269 270 //在其他框架中可以加入改代码 271 document.addEventListener("PMS_PRODUCT_CHANGE", function (e) { 272 alert(location.href + ' 收到消息(' + e.type + ')...');//弹出的消息里注意看URL的值 273 }); 274 $('#di-notity').on('click', function () { 275 //TB.Notity更多参数见\Content\TB\TB.Notity.min.js 276 277 TB.tabShow("消息测试页", "/home/NotityContent"); 278 279 setTimeout(function () { 280 TB.notity({ 281 event: 'PMS_PRODUCT_CHANGE', 282 target: 'all', 283 data: { SPUID: 1 } 284 }); 285 }, 2000); 286 }); 287 288 var cache = { 289 scanbox: null 290 }; 291 292 cache.scanbox = TB.scanbox({ 293 selecter: "#input", 294 onLoadSuccess: function (json, args) { 295 if (!!json) { 296 switch (json.type) { 297 case TB.NUMBER_RULE_PREFIX.包裹编号: 298 { 299 300 $('.media').media({ width: 0, height: 0, src: "/Media/success.wav" }); 301 TB.show(json.Message); 302 return; 303 } 304 break; 305 case TB.NUMBER_RULE_PREFIX.原包裹编号: 306 { 307 308 $('.media').media({ width: 0, height: 0, src: "/Media/success.wav" }); 309 TB.show(json.Message); 310 return; 311 } 312 break; 313 } 314 cache.view.datagrid('load'); 315 316 } else { 317 cache.scanbox.error("获取数据失败!"); 318 $('.media').media({ width: 0, height: 0, src: "/Media/error.wav" }); 319 } 320 }, 321 url: "/Logistics/ClientDelivery/InNumberClientDeliveryHandler", 322 onLoadError: function () { }, 323 onLoadBefore: function (setting, no) { 324 setting.data = { inNumber: no, packageID: cache.packageId }; 325 }, 326 onLoadError: function (setting, no) { 327 $('.media').media({ width: 0, height: 0, src: "/Media/error.wav" }); 328 } 329 330 }); 331 </script> 332 </th:block> 333 </body> 334 </html>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现