前面咱們已經進行了一些前置的操做,好比配置本地化、登陸驗證等。這一節將會作一些稍微有點難度的操做。javascript
咱們接下來要作的要求以下:要知足RestFul開發風格,以Hppt方式區分對資源的CRUD操做:URI: /資源名稱/資源表示
css
普通CURD(uri區別操做) | RestfulCRUD | |
---|---|---|
查詢 | getEmp | emp---GET |
添加 | addEmp | emp---post |
修改 | updateEmp?id=xx&xxx=xx | emp/{id}---PUT |
刪除 | deleteEmp?id=xx | emp/{id}---delete |
什麼是Restful風格? REST 指的是一組架構約束條件和原則。知足這些約束條件和原則的應用程序或設計就是 RESTful。 Web 應用程序最重要的 REST 原則是,客戶端和服務器之間的交互在請求之間是無狀態的。從客戶端到服務器的每一個請求都必須包含理解請求所必需的信息。若是服務器在請求之間的任什麼時候間點重啓,客戶端不會獲得通知。此外,無狀態請求能夠由任何可用服務器回答,這十分適合雲計算之類的環境。客戶端能夠緩存數據以改進性能。html
由此咱們能夠這樣作: ||請求URI|請求方式| |-|-|-| |查詢全部員工|emps|GET| |查詢某個員工(來到修改頁面)|emp/{id}|GET| |來到添加頁面|emp|GET| |添加員工|emp|POST| |來到修改頁面(查出員工信息進行信息回顯)|emp/{id}|GET| |修改員工|emp|PUT| |刪除員工|emp/{id}|DELETE| 按照此表進行以下的開發流程。java
package com.zhaoyi.springboot.restweb.entities; import java.util.Date; public class Employee { private Integer id; private String lastName; private String email; //1 male, 0 female private Integer gender; private Department department; private Date birth; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Integer getGender() { return gender; } public void setGender(Integer gender) { this.gender = gender; } public Department getDepartment() { return department; } public void setDepartment(Department department) { this.department = department; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } public Employee(Integer id, String lastName, String email, Integer gender, Department department) { super(); this.id = id; this.lastName = lastName; this.email = email; this.gender = gender; this.department = department; this.birth = new Date(); } public Employee() { } @Override public String toString() { return "Employee{" + "id=" + id + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + ", gender=" + gender + ", department=" + department + ", birth=" + birth + '}'; } }
關聯的部門類jquery
package com.zhaoyi.springboot.restweb.entities; public class Department { private Integer id; private String departmentName; public Department() { } public Department(int i, String string) { this.id = i; this.departmentName = string; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getDepartmentName() { return departmentName; } public void setDepartmentName(String departmentName) { this.departmentName = departmentName; } @Override public String toString() { return "Department [id=" + id + ", departmentName=" + departmentName + "]"; } }
部門數據倉庫web
package com.zhaoyi.springboot.restweb.dao; import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.zhaoyi.springboot.restweb.entities.Department; import org.springframework.stereotype.Repository; @Repository public class DepartmentDao { private static Map<Integer, Department> departments = null; static{ departments = new HashMap<Integer, Department>(); departments.put(101, new Department(101, "D-AA")); departments.put(102, new Department(102, "D-BB")); departments.put(103, new Department(103, "D-CC")); departments.put(104, new Department(104, "D-DD")); departments.put(105, new Department(105, "D-EE")); } public Collection<Department> getDepartments(){ return departments.values(); } public Department getDepartment(Integer id){ return departments.get(id); } }
員工數據倉庫spring
package com.zhaoyi.springboot.restweb.dao; import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.zhaoyi.springboot.restweb.entities.Department; import com.zhaoyi.springboot.restweb.entities.Employee; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @Repository public class EmployeeDao { private static Map<Integer, Employee> employees = null; @Autowired private DepartmentDao departmentDao; static{ employees = new HashMap<Integer, Employee>(); employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1, new Department(101, "D-AA"))); employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1, new Department(102, "D-BB"))); employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0, new Department(103, "D-CC"))); employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0, new Department(104, "D-DD"))); employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1, new Department(105, "D-EE"))); } private static Integer initId = 1006; public void save(Employee employee){ if(employee.getId() == null){ employee.setId(initId++); } employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId())); employees.put(employee.getId(), employee); } public Collection<Employee> getAll(){ return employees.values(); } public Employee get(Integer id){ return employees.get(id); } public void delete(Integer id){ employees.remove(id); } }
@Repository
將倉庫類註冊到容器中,咱們須要用的時候使用@Autowired
注入就能夠了。bootstrap
index.hmtl
的菜單欄標籤請求地址,根據上表,咱們知道應該改成/emps
請求地址:<a class="nav-link" th:href="@{/emps}">
注意將list.html放進
/resource/templates/emp/
下。如前面所說,默認狀況下模板框架會對classpath:/templates/emp/xx.html
進行渲染,其中xx
便是咱們返回的字符串視圖信息;緩存
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <div th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </div> </body> </html>
<body> ... <div th:insert="~{footer :: copy}"></div> </body
方式1:~{templatename::selector}
模板名:選擇器springboot
方式2:~{templatename::fragmentname}
模板名:片斷名 顯然這裏是第二種寫法。
其中,模板名會使用thymeleaf的先後綴配置規則進行解析。
三種引入功能片斷的th:xx
屬性:
<body> ... <div th:insert="footer :: copy"></div> <div th:replace="footer :: copy"></div> <div th:include="footer :: copy"></div> </body>
結果:
<body> ... <div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> </div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> <div> © 2011 The Good Thymes Virtual Grocery </div> </body
所以能夠總結以下:
<div></div>
)的內部;<div></div>
;<foooter>
)若是使用th:insert等屬性進行引入,能夠不用寫~{...},而行內寫法須要加上:[[~{}]]
、[(~{})]
。
這裏最好配置官方文檔食用:
8 Template Layout
按照上述知識就能夠將咱們的頁面的公共部分抽去了。
<nav th:fragment="topbar" class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
去到重複的頁面,將此處使用模板語法取代:
<nav th:replace="index::topbar"> </nav>
~{templatename::fragmentname} 模板名:片斷名
的方法替換了頂部欄的模板,接下來咱們對菜單欄(左導航)使用~{templatename::selector} 模板名:選擇器
的方式進行操做:提取
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
直接代表ID便可,無需任何
th:fragment
標籤;
替換
<div th:replace="index::#sidebar"></div>
注意ID選擇器的關鍵字
#
,不要寫漏了喲。
在實際開發中,咱們通常是不會像以前那樣作,即使使用了模板功能,你也會發現,太過繁瑣,頁面交互影響,很是難爲維護,所以,咱們須要將公共部分抽離出來,放在一個公共文件夾,這樣方便管理。 因而,咱們將全部的公共模板抽去出來,放在commons文件夾中,以下所示:
<!-- commons/bar.html --> <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 頂部模塊 --> <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" id="topbar"> ... </nav> <!-- 導航模塊 --> <nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar"> ... </nav> </body> </html>
此處我已經將其修改成相同的模式,即只須要用ID引入便可。省去了th代碼塊。
同時,刪除index.html和list.html中的相關部分,都加入導入模塊代碼,他們應該由
<div th:replace="index::#sidebar"></div>
修改成
<div th:replace="commons/bar::#sidebar"></div>
這樣的模式。
其中
commons/bar
恰好對應於commons下的bar文件,sidebar對應引用對應模塊的id。
咱們還發現有一個問題,那就是菜單欄的菜單高亮問題,好比咱們點擊首頁,應該dashboard菜單欄高亮,點擊員工列表,員工列表高亮,咱們觀察html樣式發現,其實就是他們是否具有active
這個class與否的問題,這就須要咱們作一件事,那就是在進行片斷引入的時候,進行參數傳遞,告訴當前片斷我是什麼頁面進行了引入,其實就是一個傳遞參數的過程,讓當前片斷根據參數的值生成具體的片斷行爲,查看thymeleaf官方文檔瞭解相應的傳參方式:
<div th:replace="::frag (twovar=${value2},onevar=${value1})">...</div>
在看看具體的片斷(bar.html中的片斷)接收參數的方式:
<div th:fragment="frag (onevar,twovar)"> <p th:text="${onevar} + ' - ' + ${twovar}">...</p> </div>
另外,還能夠不用特定的去寫接受參數的方式,能夠直接在片斷裏面直接引用變量便可。所以,咱們能夠這樣寫:
<div th:replace="commons/bar::#sidebar(activeUri='index')"></div>
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
... <a class="nav-link active" th:class="${activeUri=='index'? 'nav-link active':'nav-link'}" th:href="@{/index}"> ... <a class="nav-link" th:class="${activeUri=='emps'? 'nav-link active':'nav-link'}" th:href="@{/emps}">
這樣,就能夠保證傳遞根據咱們的不一樣頁面,高亮對應的菜單模塊了。
在list.html
頁面中,將咱們返回的emps列表顯示,代碼以下:
<div class="container-fluid"> <div class="row"> <div th:replace="commons/bar::#sidebar(activeUri='emps')"></div> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <h2><a class="btn btn-sm btn-primary">add</a></h2> <div class="table-responsive"> <table class="table table-striped table-sm"> <thead> <tr> <th>#</th> <th>lastName</th> <th>email</th> <th>gender</th> <th>department</th> <th>birth</th> <th>operate</th> </tr> </thead> <tbody> <tr th:each="emp:${emps}"> <td th:text="${emp.id}"></td> <td th:text="${emp.lastName}"></td> <td th:text="${emp.email}"></td> <td th:text="${emp.gender} == 0? '男':'女'"></td> <td th:text="${emp.department.departmentName}"></td> <td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd:HH:mm')}"></td> <td><a class="btn btn-sm btn-default">edit</a> <a class="btn btn-sm btn-warning">delete</a> </td> </tr> </tbody> </table> </div> </main> </div> </div>
其中:
th:each
屬性會由於每一次遍歷生成一次所在標籤;@Controller public class EmployeeController { @Autowired private EmployeeDao employeeDao; @RequestMapping("/emps") @GetMapping public String emps(Model model){ model.addAttribute("emps", employeeDao.getAll()); return "emp/list"; } /** * 接收前往員工添加頁面的請求,並跳轉到添加頁面emp/add.html * @return */ @RequestMapping("/emp") @GetMapping public String toAddPage(){ return "emp/add"; } }
<!DOCTYPE html> <!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ --> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>Dashboard Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link th:href="@{asserts/css/bootstrap.min.css}" rel="stylesheet"> <!-- Custom styles for this template --> <link th:href="@{asserts/css/dashboard.css}" rel="stylesheet"> <style type="text/css"> /* Chart.js */ @-webkit-keyframes chartjs-render-animation { from { opacity: 0.99 } to { opacity: 1 } } @keyframes chartjs-render-animation { from { opacity: 0.99 } to { opacity: 1 } } .chartjs-render-monitor { -webkit-animation: chartjs-render-animation 0.001s; animation: chartjs-render-animation 0.001s; } </style> </head> <body> <div th:replace="commons/bar::#topbar"></div> <div class="container-fluid"> <div class="row"> <div th:replace="commons/bar::#sidebar(activeUri='emps')"></div> <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <form> <div class="form-group"> <label>LastName</label> <input type="text" class="form-control" placeholder="zhangsan"> </div> <div class="form-group"> <label>Email</label> <input type="email" class="form-control" placeholder="zhangsan@atguigu.com"> </div> <div class="form-group"> <label>Gender</label><br/> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="1"> <label class="form-check-label">男</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" name="gender" value="0"> <label class="form-check-label">女</label> </div> </div> <div class="form-group"> <label>department</label> <select class="form-control"> <option>1</option> <option>2</option> <option>3</option> <option>4</option> <option>5</option> </select> </div> <div class="form-group"> <label>Birth</label> <input type="text" class="form-control" placeholder="zhangsan"> </div> <button type="submit" class="btn btn-primary">添加</button> </form> </main> </div> </div> <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script> <script type="text/javascript" src="asserts/js/popper.min.js"></script> <script type="text/javascript" src="asserts/js/bootstrap.min.js"></script> <!-- Icons --> <script type="text/javascript" src="asserts/js/feather.min.js"></script> <script> feather.replace() </script> <!-- Graphs --> <script type="text/javascript" src="asserts/js/Chart.min.js"></script> </body> </html>
完成以後運行咱們還發現了一個問題,那就是部門選擇哪一個地方,實際上是來源自咱們後臺的部門信息來生成所以,咱們跳轉到添加員工頁面時,還應該將部門信息所有傳遞過來:
.... @Autowired private DepartmentDao departmentDao; ... /** * 接收前往員工添加頁面的請求,並跳轉到添加頁面emp/add.html * @return */ @RequestMapping("/emp") @GetMapping public String toAddPage(Model model){ Collection<Department> departments = departmentDao.getDepartments(); model.addAttribute("departments", departments); return "emp/add"; } }
<!DOCTYPE html> <!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ --> .... <div class="form-group"> <label>department</label> <select class="form-control"> <option th:each="department:${departments}" th:value="${department.id}" th:text="${department.departmentName}"></option> </select> </div> ...