代碼鏈接
Springboot的簡(jiǎn)單RestfulCRUD實(shí)驗(yàn)(上)
三苛聘、CRUD-員工列表
實(shí)驗(yàn)要求:
1)设哗、RestfulCRUD:CRUD滿足Rest風(fēng)格;
URI: /資源名稱/資源標(biāo)識(shí) HTTP請(qǐng)求方式區(qū)分對(duì)資源CRUD操作
2)震缭、實(shí)驗(yàn)的請(qǐng)求架構(gòu)
效果圖:
3)拣宰、員工列表:
thymeleaf公共頁(yè)面元素抽取
1巡社、抽取公共片段
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
2手趣、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名::選擇器
~{templatename::fragmentname}:模板名::片段名
3绿渣、默認(rèn)效果:
insert的公共片段在div標(biāo)簽中
如果使用th:insert等屬性進(jìn)行引入,可以不用寫~{}:
行內(nèi)寫法可以加上:[[~{}]];[(~{})]潜圃;
三種引入公共片段的th屬性:
th:insert:將公共片段整個(gè)插入到聲明引入的元素中
th:replace:將聲明引入的元素替換為公共片段
th:include:將被引入的片段的內(nèi)容包含進(jìn)這個(gè)標(biāo)簽中
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
引入方式
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
效果
<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>
引入片段的時(shí)候傳入?yún)?shù):
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active"
th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}"
href="#" th:href="@{/main.html}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
Dashboard <span class="sr-only">(current)</span>
</a>
</li>
<!--引入側(cè)邊欄;傳入?yún)?shù)-->
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
6)谭期、CRUD-員工添加
添加頁(yè)面
<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>
提交的數(shù)據(jù)格式不對(duì):生日:日期踏志;
2017-12-12胀瞪;2017/12/12;2017.12.12圆雁;
日期的格式化帆谍;SpringMVC將頁(yè)面提交的值需要轉(zhuǎn)換為指定的類型;
2017-12-12---Date汛蝙; 類型轉(zhuǎn)換,格式化;
默認(rèn)日期是按照/的方式坚洽;
7)西土、CRUD-員工修改
修改添加二合一表單
<!--需要區(qū)分是員工修改還是添加;-->
<form th:action="@{/emp}" method="post">
<!--發(fā)送put請(qǐng)求修改員工數(shù)據(jù)-->
<!--
1绘雁、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自動(dòng)配置好的)
2、頁(yè)面創(chuàng)建一個(gè)post表單
3欣除、創(chuàng)建一個(gè)input項(xiàng),name="_method";值就是我們指定的請(qǐng)求方式
-->
<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
<div class="form-group">
<label>LastName</label>
<input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}">
</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" th:checked="${emp!=null}?${emp.gender==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" th:checked="${emp!=null}?${emp.gender==0}">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<!--提交的是部門的id-->
<select class="form-control" name="department.id">
<option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">
</div>
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
</form>
8)、CRUD-員工刪除
<tr th:each="emp:${emps}">
<td th:text="${emp.id}"></td>
<td>[[${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-primary" th:href="@{/emp/}+${emp.id}">編輯</a>
<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">刪除</button>
</td>
</tr>
<script>
$(".deleteBtn").click(function(){
//刪除當(dāng)前員工的
$("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit();
return false;
});
</script>
四、錯(cuò)誤處理機(jī)制
1)禽拔、SpringBoot默認(rèn)的錯(cuò)誤處理機(jī)制
默認(rèn)效果:
?1)、瀏覽器硫惕,返回一個(gè)默認(rèn)的錯(cuò)誤頁(yè)面恼除,瀏覽器發(fā)送請(qǐng)求的請(qǐng)求頭:
2)、如果是其他客戶端令野,默認(rèn)響應(yīng)一個(gè)json數(shù)據(jù)
原理:
? 可以參照ErrorMvcAutoConfiguration徽级;錯(cuò)誤處理的自動(dòng)配置;
給容器中添加了以下組件
? 1堵幽、DefaultErrorAttributes:
幫我們?cè)陧?yè)面共享信息弹澎;
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, requestAttributes);
addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
addPath(errorAttributes, requestAttributes);
return errorAttributes;
}
2苦蒿、BasicErrorController:處理默認(rèn)/error請(qǐng)求
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@RequestMapping(produces = "text/html")//產(chǎn)生html類型的數(shù)據(jù);瀏覽器發(fā)送的請(qǐng)求來(lái)到這個(gè)方法處理
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//去哪個(gè)頁(yè)面作為錯(cuò)誤頁(yè)面团滥;包含頁(yè)面地址和頁(yè)面內(nèi)容
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}
@RequestMapping
@ResponseBody //產(chǎn)生json數(shù)據(jù)灸姊,其他客戶端來(lái)到這個(gè)方法處理秉溉;
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<Map<String, Object>>(body, status);
}
3召嘶、ErrorPageCustomizer:
@Value("${error.path:/error}")
private String path = "/error"; 系統(tǒng)出現(xiàn)錯(cuò)誤以后來(lái)到error請(qǐng)求進(jìn)行處理;(web.xml注冊(cè)的錯(cuò)誤頁(yè)面規(guī)則)
4甲喝、DefaultErrorViewResolver
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默認(rèn)SpringBoot可以去找到一個(gè)頁(yè)面铛只? error/404
String errorViewName = "error/" + viewName;
//模板引擎可以解析這個(gè)頁(yè)面地址就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
//模板引擎可用的情況下返回到errorViewName指定的視圖地址
return new ModelAndView(errorViewName, model);
}
//模板引擎不可用,就在靜態(tài)資源文件夾下找errorViewName對(duì)應(yīng)的頁(yè)面 error/404.html
return resolveResource(errorViewName, model);
}
步驟:
? 一但系統(tǒng)出現(xiàn)4xx或者5xx之類的錯(cuò)誤诵冒;ErrorPageCustomizer就會(huì)生效(定制錯(cuò)誤的響應(yīng)規(guī)則)谊惭;就會(huì)來(lái)到/error請(qǐng)求;就會(huì)被BasicErrorController處理豹芯;
? 1)響應(yīng)頁(yè)面驱敲;去哪個(gè)頁(yè)面是由DefaultErrorViewResolver解析得到的众眨;
protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
//所有的ErrorViewResolver得到ModelAndView
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
2)、如何定制錯(cuò)誤響應(yīng):
1)沿腰、如何定制錯(cuò)誤的頁(yè)面狈定;
? (1)、有模板引擎的情況下措嵌;error/狀態(tài)碼; 【將錯(cuò)誤頁(yè)面命名為 錯(cuò)誤狀態(tài)碼.html 放在模板引擎文件夾里面的 error文件夾下】芦缰,發(fā)生此狀態(tài)碼的錯(cuò)誤就會(huì)來(lái)到 對(duì)應(yīng)的頁(yè)面让蕾;
? 我們可以使用4xx和5xx作為錯(cuò)誤頁(yè)面的文件名來(lái)匹配這種類型的所有錯(cuò)誤,精確優(yōu)先(優(yōu)先尋找精確的狀態(tài)碼.html);
? 頁(yè)面能獲取的信息神帅;
? timestamp:時(shí)間戳
? status:狀態(tài)碼
? error:錯(cuò)誤提示
? exception:異常對(duì)象
? message:異常消息
? errors:JSR303數(shù)據(jù)校驗(yàn)的錯(cuò)誤都在這里
? (2)找御、沒(méi)有模板引擎(模板引擎找不到這個(gè)錯(cuò)誤頁(yè)面)绍填,靜態(tài)資源文件夾下找讨永;
? (3)遇革、以上都沒(méi)有錯(cuò)誤頁(yè)面,就是默認(rèn)來(lái)到SpringBoot默認(rèn)的錯(cuò)誤提示頁(yè)面锻霎;
2)揪漩、如何定制錯(cuò)誤的json數(shù)據(jù);
(1)冰更、自定義異常處理&返回定制json數(shù)據(jù)昂勒;
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
//沒(méi)有自適應(yīng)效果...
(2)叁怪、轉(zhuǎn)發(fā)到/error進(jìn)行自適應(yīng)響應(yīng)效果處理
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
//傳入我們自己的錯(cuò)誤狀態(tài)碼 4xx 5xx,否則就不會(huì)進(jìn)入定制錯(cuò)誤頁(yè)面的解析流程
/**
* Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
*/
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.notexist");
map.put("message",e.getMessage());
//轉(zhuǎn)發(fā)到/error
return "forward:/error";
}
(3)涣觉、將我們的定制數(shù)據(jù)攜帶出去血柳;
出現(xiàn)錯(cuò)誤以后难捌,會(huì)來(lái)到/error請(qǐng)求,會(huì)被BasicErrorController處理员淫,響應(yīng)出去可以獲取的數(shù)據(jù)是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)規(guī)定的方法)击敌;
? 1、完全來(lái)編寫一個(gè)ErrorController的實(shí)現(xiàn)類【或者是編寫AbstractErrorController的子類】圣蝎,放在容器中;
? 2牲证、頁(yè)面上能用的數(shù)據(jù)关面,或者是json返回能用的數(shù)據(jù)都是通過(guò)errorAttributes.getErrorAttributes得到缭裆;
3、容器中DefaultErrorAttributes.getErrorAttributes()辛燥;默認(rèn)進(jìn)行數(shù)據(jù)處理的缝其;自定義ErrorAttributes
//給容器中加入我們自己定義的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("company","atguigu");
return map;
}
}
最終的效果:響應(yīng)是自適應(yīng)的内边,可以通過(guò)定制ErrorAttributes改變需要返回的內(nèi)容