SpringMVC 介紹
- 視圖 view
- 數據模型 model
- 視圖解析器 viewResolver
- 處理適配器 HandlerAdapter
設計理念
-
model — view—controller
-
http請求 ——控制器—— 業務層
-
控制器——相應請求——視圖層
-
業務層(包含nosql緩存)——數據訪問層(關係數據庫,包含事務)
流程
- 圍繞DispatcherServlet
- 加入@ResponseBody是 沒有經過視圖解析器和視圖渲染的
-
http Request——dispatcher servlet
-
處理器映射:定位控制器 相應方法(handlerMapping)
- 處理器執行鏈:包含處理器 及其攔截器(handlerExecutionChain)
- dispatcher servlet
-
處理適配器:運行處理器 (HandlerAdapter)
-
模型與視圖:獲取視圖和模型數據 (Model And View)——dispatcher servlet
-
視圖解析器:定位視圖資源 (viewResolver)
-
視圖:將數據模型渲染展示(view)
- 請求 到達—— dispatcher servlet
- dispatcher servlet 找 ——處理器映射 (handlerMapping)找——處理器執行鏈(handlerExecutionChain)
- 返回到 dispatcher servlet ,在找 —— 處理適配器 (handlerAdapter)—— 最後生成 model and view
- 返回到 dispatcher servlet,找 視圖解析器
- 找 視圖,將數據渲染展示
1.控制器
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService = null;
// 展示用戶詳情
@RequestMapping("details")
public ModelAndView details(Long id) {
// 訪問模型層得到數據
User user = userService.getUser(id);
// 模型和視圖
ModelAndView mv = new ModelAndView();
// 定義模型視圖
mv.setViewName("user/details");
// 加入數據模型
mv.addObject("user", user);
// 返回模型和視圖
return mv;
}
}
-
@RequestMapping(“details”) 請求路徑,MVC 啓動時,就被掃描到 HandlerMapping(處理器映射)的機制中存儲
-
用戶發起 DispatcherServlet攔截後,HandlerMapper機制 找到對應的控制器 進行相應
-
HandlerMapping 返回一個 HandlerExecutionChain對象
-
handlerExecutionChain
public class HandlerExecutionChain { private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class); private final Object handler;//包含一個處理器 @Nullable private HandlerInterceptor[] interceptors; @Nullable private List<HandlerInterceptor> interceptorList; private int interceptorIndex; }
- private final Object handler;//包含一個處理器,對控制器的封裝
- 控制器有多個參數,處理器就可讀入http和上下文的相關參數
- 執行完後,又可以通過配置信息,對控制器的返回結果進行處理
- 處理器包含控制器方法的邏輯,還有處理器的攔截器 interceptor
- 得到了處理器 我們有普通的http請求,BeanName的請求 和 webSocket請求
- 還需要一個適配器去 運行 HandlerExecutionChain 對象包含的處理器
- 這就是 HandlerAdapter 接口的實現類
- 最常用的是:HttpRequestHandlerAdapter
2.jsp pom
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<scope>provided</scope>
</dependency>
3. properties配置
-
爲了定製 InternalResourceViewResolver初始化,配置
spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp
- 比如控制器返回:user/details ,則會找 /WEB-INF/jsp/user/detail.jsp
4.jsp視圖
<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>用戶詳情</title>
</head>
<body>
<center>
<table border="1">
<tr>
<td>標籤</td>
<td>值</td>
</tr>
<tr>
<td>用戶編號</td>
<td><c:out value="${user.id}"></c:out></td>
</tr>
<tr>
<td>用戶名稱</td>
<td><c:out value="${user.userName}"></c:out></td>
</tr>
<tr>
<td>用戶備註</td>
<td><c:out value="${user.note}"></c:out></td>
</tr>
</table>
</center>
</body>
</html>
啓動類
@SpringBootApplication(scanBasePackages = "com.springboot.chapter9")
@MapperScan(basePackages="com.springboot.chapter9", annotationClass = Mapper.class)
public class Chapter9Application {
public static void main(String[] args) {
SpringApplication.run(Chapter9Application.class, args);
}
}
使用JSON視圖
@RequestMapping("/detailsForJson")
public ModelAndView detailsForJson(Long id) {
// 訪問模型層得到數據
//User user = userService.getUser(id);
User user=new User();
user.setId(111L);
user.setUserName("張三");
// 模型和視圖
ModelAndView mv = new ModelAndView();
// 生成JSON視圖
MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
mv.setView(jsonView);
// 加入模型
mv.addObject("user", user);
return mv;
}
Spring MVC 下的流程
1.請求 ——
2.dispatcherServlet —— handlerMapping (@RequestMapping提供URI和其他配置) —— handlerExecutionChain (返回包含控制器邏輯的處理器)
—— dispatcherServlet
—— 3.運行處理器 ——httpRequestHandlerAdapter
——4.modelAndView (視圖名稱:user/details數據模型爲用戶模型)
——dispatcherServlet
——5. InternalResourceViewResolver 定位視圖 ,前綴+視圖名稱+ 後綴
——6. dispatcherServlet —— 將用戶模型渲染到視圖展示。 details.jsp
‘
定製Spring MVC 的初始化
-
Spring MVC 3.1開始,支持無web.xml
-
提供了 WebMvcConfigurer
-
Spring Boot中 配置類 WebMVCAutoConfiguration定義 (繼承上面的)
- 有一個靜態內部類,WebMvcAutoConfigurationAdapter
-
WebMvcConfigurer 初始化接口,java 8的接口
-
WebMvcAutoConfigurationAdapter 繼承上面,是boot的 配置類WebMVCAutoConfiguration 的內部類
-
會讀取 Spring MVC的屬性來初始化組件。
spring:
mvc:
async:
request-timeout: #異步請求的超時時間
contentnegotiation:
favor-parameter: false #是否使用請求參數 (默認format) 來確認請求的媒體類型
date-format: #廢棄,日期的格式配置
dispatch-trace-request: false #是否支持trace請求
dispatch-options-request: false #是否支持options請求
favicon:
enabled: false #廢棄,是否啓用圖標
formcontent:
putfilter:
enabled: false #廢棄
locale: af #默認國際化選項
log-request-details: false
log-resolved-exception: false #是否啓用警告日誌解決問題
servlet:
load-on-startup: -1
static-path-pattern: /** #指定靜態文件
throw-exception-if-no-handler-found: false #找不到請求,就炮NoHandlerFoundException異常
view:
prefix: #前綴
suffix: #後綴
spring MVC實例
@RequestMapping("/table")
public ModelAndView table() {
// 訪問模型層得到數據
List<User> userList = userService.findUsers(null, null);
// 模型和視圖
ModelAndView mv = new ModelAndView();
// 定義模型視圖
mv.setViewName("user/table");
// 加入數據模型
mv.addObject("userList", userList);
// 返回模型和視圖
return mv;
}
@RequestMapping("/list")
@ResponseBody
public List<User> list(@RequestParam(value = "userName", required = false) String userName,
@RequestParam(value = "note", required = false) String note) {
// 訪問模型層得到數據
List<User> userList = userService.findUsers(userName, note);
return userList;
}
@Repository
public interface UserDao {
User getUser(Long id);
List<User> findUsers(@Param("userName") String userName, @Param("note") String note);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hua.testj.UserDao">
<select id="getUser" resultType="user">
select id, user_name as userName, note from t_user where id = #{id}
</select>
<select id="findUsers" resultType="user">
select id, user_name as userName, note from t_user
<where>
<if test="userName != null"> and user_name like concat('%', #{userName}, '%')</if>
<if test="note != null"> and note like concat('%', #{note}, '%')</if>
</where>
</select>
</mapper>
<if test="userName != null"> and user_name like concat('%', #{userName}, '%')</if>
like concat('%', #{userName}, '%')
jsp
<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用戶列表</title>
<link rel="stylesheet" type="text/css"
href="../../easyui/themes/default/easyui.css">
<link rel="stylesheet" type="text/css"
href="../../easyui/themes/icon.css">
<link rel="stylesheet" type="text/css" href="../../easyui/demo/demo.css">
<script type="text/javascript" src="../../easyui/jquery.min.js"></script>
<script type="text/javascript" src="../../easyui/jquery.easyui.min.js"></script>
<script type="text/javascript">
// 定義事件方法
function onSearch() {
// 指定請求路徑
var opts = $("#dg").datagrid("options");
opts.url = "./list";
// 獲取查詢參數
var userName = $("#userName").val();
var note = $("#note").val();
// 組織參數
var params = {};
if (userName != null && userName.trim() != '') {
params.userName = userName;
}
if (note != null && note.trim() != '') {
params.note = note;
}
// 重新載入表格數據
$("#dg").datagrid('load', params);
}
</script>
</head>
<body>
<div style="margin: 20px 0;"></div>
<div class="easyui-layout" style="width: 100%; height: 350px;">
<div data-options="region:'north'" style="height: 50px">
<form id="searchForm" method="post">
<table>
<tr>
<td>用戶名稱:</td>
<td><input id="userName" name="userName"
class="easyui-textbox" data-options="prompt:'輸入用戶名稱...'"
style="width: 100%; height: 32px"></td>
<td>備註</td>
<td><input id="note" name="note" class="easyui-textbox"
data-options="prompt:'輸入備註...'" style="width: 100%; height: 32px">
</td>
<td><a href="#" class="easyui-linkbutton"
data-options="iconCls:'icon-search'" style="width: 80px"
οnclick="onSearch()">查詢</a></td>
</tr>
</table>
</form>
</div>
<div data-options="region:'center',title:'用戶列表',iconCls:'icon-ok'">
<table id="dg" class="easyui-datagrid"
,
data-options="border:false,singleSelect:true,
fit:true,fitColumns:true">
<thead>
<tr>
<th data-options="field:'id'" width="80">編號</th>
<th data-options="field:'userName'" width="100">用戶名稱</th>
<th data-options="field:'note'" width="80">備註</th>
</tr>
</thead>
<tbody>
<!--使用forEache渲染數據模型-->
<c:forEach items="${userList}" var="user">
<tr>
<td>${user.id}</td>
<td>${user.userName}</td>
<td>${user.note}</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</body>
-
/easyui/ 放在與webapp的子目錄
深入 Spring MVC開發
處理器映射
- 就會將註解 @RequestMapping所配置的內容 保存到 處理器映射 HandlerMapping 機制中去
- 通過 HandlerMapping 進行匹配
- 找到對應的處理器
- 並且將處理器攔截保存到 handlerExecutionChain 對象中
- 返回給 dispatcherServlet
- handlerMapping任務是:將請求定位到具體的處理器上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default ""; //請求映射名稱
@AliasFor("path")
String[] value() default {}; //通過路徑映射
@AliasFor("value")
String[] path() default {}; //路徑映射回 path配置項
RequestMethod[] method() default {};//限定只相應http請求類型
//默認所有。 get post head options put trace
String[] params() default {}; //存在http參數時才相應
String[] headers() default {};//限定請求頭存在對應的參數
String[] consumes() default {};//限定http請求體 提交類型。 application/json test/html
String[] produces() default {};//限定返回的內容類型,僅當http請求頭中 accept類型中包含該指定類型時 才返回
}
- spring 4.3 後,@GetMapping @PostMapping @PatchMapping @PutMapping
- @DeleteMapping
獲取控制器參數
-
處理器是對控制器的包裝
-
在進入控制器方法之前,會對http的參數 和 上下文進行解析。
無註解獲取
- 允許參數爲空,參數名稱 和 http請求的參數名稱一致
@RequestMapping("/my")
@Controller
public class MyController {
/**
* 在無註解下獲取參數,要求參數名稱和HTTP請求參數名稱一致.
* @param intVal -- 整數
* @param longVal -- 長整形
* @param str --字符串
* @return 響應JSON參數
*/
// HTTP GET請求
@GetMapping("/no/annotation")
@ResponseBody
public Map<String, Object> noAnnotation(Integer intVal, Long longVal, String str) {
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("intVal", intVal);
paramsMap.put("longVal", longVal);
paramsMap.put("str", str);
return paramsMap;
}
}
-
- http://localhost:8080/user/no/annotation?intVal=10&longVal=200
@RequestParam獲取參數
-
RequestParam 確定前後端參數名稱的映射關係
/** * 通過註解@RequestParam獲取參數 * @param intVal -- 整數 * @param longVal-- 長整形 * @param str --字符串 * @return 響應JSON數據集 */ @GetMapping("/annotation") @ResponseBody public Map<String, Object> requestParam( @RequestParam("int_val") Integer intVal, @RequestParam("long_val") Long longVal, @RequestParam("str_val") String strVal) { Map<String, Object> paramsMap = new HashMap<>(); paramsMap.put("intVal", intVal); paramsMap.put("longVal", longVal); paramsMap.put("strVal", strVal); return paramsMap; }
- 默認情況下 RequestParam 不能爲空
- 想要爲空 @RequestParam(value = “long_val”,required = false)
- http://localhost:8080/my/annotation?int_val=10&str_val=哈哈
傳遞數組
@GetMapping("/requestArray")
@ResponseBody
public Map<String, Object> requestArray(int[] intArr, Long[] longArr, String[] strArr) {
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("intArr", intArr);
paramsMap.put("longArr", longArr);
paramsMap.put("strArr", strArr);
return paramsMap;
}
- 已經支持 用 逗號分隔 的數組
- http://localhost:8080/my/requestArray?intArr=1,2,3,4
傳遞json
<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>新增用戶用戶</title>
<!-- 加載Query文件-->
<script src="https://code.jquery.com/jquery-3.2.0.js">
</script>
<script type="text/javascript">
$(document).ready(function() {
$("#submit").click(function() {
var userName = $("#userName").val();
var note = $("#note").val();
if ($.trim(userName)=='') {
alert("用戶名不能爲空!");
return;
}
var params = {
userName : userName,
note : note
};
$.post({
url : "./insert",
// 此處需要告知傳遞參數類型爲JSON,不能缺少
contentType : "application/json",
// 將JSON轉化爲字符串傳遞
data : JSON.stringify(params),
// 成功後的方法
success : function(result) {
if (result == null || result.id == null) {
alert("插入失敗");
return;
}
alert("插入成功");
}
});
});
});
</script>
</head>
<body>
<div style="margin: 20px 0;"></div>
<form id="insertForm">
<table>
<tr>
<td>用戶名稱:</td>
<td><input id="userName" name="userName"></td>
</tr>
<tr>
<td>備註</td>
<td><input id="note" name="note"></td>
</tr>
<tr>
<td></td>
<td align="right"><input id="submit" type="button" value="提交" /></td>
</tr>
</table>
</form>
</body>
var params = {
userName : userName,
note : note
};
$.post({
url : "./insert",
// 此處需要告知傳遞參數類型爲JSON,不能缺少
contentType : "application/json",
// 將JSON轉化爲字符串傳遞
data : JSON.stringify(params),
// 成功後的方法
success : function(result) {
if (result == null || result.id == null) {
alert("插入失敗");
return;
}
alert("插入成功");
}
});
@Controller
@RequestMapping("/user")
public class UserController {
// 注入用戶服務類
@Autowired
private UserService userService = null;
/**
* 打開請求頁面
* @return 字符串,指向頁面
*/
@GetMapping("/add")
public String add() {
return "/user/add";
}
/**
* 新增用戶
* @param user 通過@RequestBody註解得到JSON參數
* @return 回填id後的用戶信息
*/
@PostMapping("/insert")
@ResponseBody
public User insert(@RequestBody User user) {
userService.insertUser(user);
return user;
}
}
通過 URL傳遞參數
- rest ,編號爲1,url 爲: /user/1
// {...}代表佔位符,還可以配置參數名稱
@GetMapping("/{id}")
// 響應爲JSON數據集
@ResponseBody
// @PathVariable通過名稱獲取參數
public User get(@PathVariable("id") Long id) {
return userService.getUser(id);
}
獲取格式化參數
-
日期: yyyy-MM-dd
-
金額 約定爲 貨幣符號 和 逗號 。 $1,000,00.00
-
@DataTimeFormat
-
@NumberFormat
<%@ page pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>格式化</title> </head> <body> <form action="./commit" method="post"> <table> <tr> <td>日期(yyyy-MM-dd)</td> <td><input type="text" name="date" value="2017-08-08" /></td> </tr> <tr> <td>金額(#,###.##)</td> <td><input type="text" name="number" value="1,234,567.89" /></td> </tr> <tr> <td colspan="2" align="right"><input type="submit" value="提交" /> </td> </tr> </table> </form> </body> </html>
// 映射JSP頁面 @GetMapping("/format/form") public String showFormat() { return "/format/formatter"; } // 獲取提交參數 @PostMapping("/format/commit") @ResponseBody public Map<String, Object> format(Date date, @NumberFormat(pattern = "#,###.##") Double number) { Map<String, Object> dataMap = new HashMap<>(); dataMap.put("date", date); dataMap.put("number", number); return dataMap; }
-
spring.mvc.date-format=yyyy-MM-dd 或者: @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date date @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-
value="2017-08-08" data傳遞string 可以解析 正規的傳遞爲:2020-06-12 00:00:00 。後臺要想要時分秒,用string接收吧 value="1,234,567.89" 必須設置格式化,才能被識別
-
自定義參數轉換規則
- 密文傳輸
- 轉換規則還包括,控制器返回後的處理。
- http請求包含, 請求頭header,請求體body,url。上下文環境 和 客戶端交互的會話session機制
- 消息轉換是 請求體的轉換
- 先從 http請求 和 上下文環境 得到參數
- 簡易參數 以簡單的轉換器 (spring MVC 已經提供)
- 轉換 http請求體 body,調用 httpMessageConverter接口
@PostMapping("/insert")
@ResponseBody
public User insert(@RequestBody User user) {
userService.insertUser(user);
return user;
}
-
httpMessageConverter
- canRead
- canWrite
- getSupportedMediaTypes
- read
- write
-
參數標註@RequestBody,首先會調度 canRead方法確定請求體 是否可讀
-
可讀後,使用read方法,將前端提交的json轉換爲控制器
-
性別前端傳遞控制器的是整數,而控制器參數卻是一個枚舉,需要自定義參數轉換規則
-
處理器轉換參數的過程,
-
是通過webDataBinder機制來獲取參數的,
-
解析Http請求的上下文,
-
在控制器的 調用之前轉換參數 並且 提供驗證的功能
-
爲調用控制器方法做準備
-
處理器會 從 HTTP 請求中 讀取數據,然後通過三種接口來進行 各類參數轉換
- convert
- formatter
- genericConverter
-
這三種接口 都採用了 註冊機 的機制,默認MVC已經在註冊機內 註冊了許多的轉換器
-
整型 長整型 字符串
-
當需要自定義轉換規則時,只需要在註冊機上註冊自己的轉換器就可以了。
-
webDataBinder 還有一個功能,那就是驗證轉換結果
-
有了參數的轉換和驗證,最終控制器就 可以得到 合法的結果
-
處理 內部處理邏輯 httpMessageConverter
- WebDataBinder
- converter 普通轉換器 integer,從http得到字符串轉成integer
- formatter 格式化轉換器,日期
- genericConverter http參數 轉換成數組
- Java Pojo
- 驗證機制
- 調用控制器
-
數據類型的轉換,MVC提供了一個服務機制,ConversionService接口
-
默認用 DefaultFormattingConversionService
-
Boot 還提供了特殊的機制來管理 這些轉換器
-
WebMVCAutoConfiguration 定義了內部類 WebMvcAutoConfigurationAdapter
一對一轉換器
/**
* 自定義字符串用戶轉換器
*/
@Component
public class StringToUserConverter implements Converter<String, User> {
/**
* 轉換方法
*/
@Override
public User convert(String userStr) {
User user = new User();
String []strArr = userStr.split("-");
Long id = Long.parseLong(strArr[0]);
String userName = strArr[1];
String note = strArr[2];
user.setId(id);
user.setUserName(userName);
user.setNote(note);
return user;
}
}
-
@GetMapping("/converter")@ResponseBodypublic User getUserByConverter(User user) { return user;}
{
"id": 1,
"userName": "張三",
"note": "備註一下"
}
GenericConverter集合 和 數組 轉換
-
MVC 會使用 StringToCollectionConverter轉換它
-
會把 字符串用逗號 分隔 爲 一個個的子 字符串
-
然後根據 原類型 String,目標類型 User ,找到對應的轉換器
-
StringToCollectionConver —— StringToUserConverter
-
@GetMapping("/list") @ResponseBody public List<User> list(List<User> userList) { return userList; }
-
http://localhost:8080/user/list?userList=1-張三-備註一下,2-李四-備註二下
數據驗證
- 參數轉換之後,需要驗證參數的合法性
- JSR-303註解驗證,默認會引入 hibernate validator
- 自定義驗證規則
jsr-303驗證
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
java
public class ValidatorPojo {
// 非空判斷
@NotNull(message = "id不能爲空")
private Long id;
@Future(message = "需要一個將來日期") // 只能是將來的日期
// @Past //只能去過去的日期
@DateTimeFormat(pattern = "yyyy-MM-dd") // 日期格式化轉換
@NotNull // 不能爲空
private Date date;
@NotNull // 不能爲空
@DecimalMin(value = "0.1") // 最小值0.1元
@DecimalMax(value = "10000.00") // 最大值10000元
private Double doubleValue = null;
@Min(value = 1, message = "最小值爲1") // 最小值爲1
@Max(value = 88, message = "最大值爲88") // 最大值88
@NotNull // 不能爲空
private Integer integer;
@Range(min = 1, max = 888, message = "範圍爲1至888") // 限定範圍
private Long range;
// 郵箱驗證
@Email(message = "郵箱格式錯誤")
private String email;
@Size(min = 20, max = 30, message = "字符串長度要求20到30之間。")
private String size;
}
pojo.jsp
<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>測試JSR-303</title>
<!-- 加載Query文件-->
<script src="https://code.jquery.com/jquery-3.2.0.js"></script>
<script type="text/javascript">
$(document).ready(function() {
// 請求驗證的POJO
var pojo = {
id : null,
date : '2017-08-08',
doubleValue : 999999.09,
integer : 100,
range : 1000,
email : 'email',
size : 'adv1212',
regexp : 'a,b,c,d'
}
$.post({
url : "./validate",
// 此處需要告知傳遞參數類型爲JSON,不能缺少
contentType : "application/json",
// 將JSON轉化爲字符串傳遞
data : JSON.stringify(pojo),
// 成功後的方法
success : function(result) {
}
});
});
</script>
</head>
<body></body>
</html>
controller
@GetMapping("/valid/page")
public String validPage() {
return "/validator/pojo";
}
/***
* 解析驗證參數錯誤
* @param vp —— 需要驗證的POJO,使用註解@Valid 表示驗證
* @param errors 錯誤信息,它由Spring MVC通過驗證POJO後自動填充
* @return 錯誤信息Map
*/
@RequestMapping(value = "/valid/validate")
@ResponseBody
public Map<String, Object> validate(
@Valid @RequestBody ValidatorPojo vp, Errors errors) {
Map<String, Object> errMap = new HashMap<>();
// 獲取錯誤列表
List<ObjectError> oes = errors.getAllErrors();
for (ObjectError oe : oes) {
String key = null;
String msg = null;
// 字段錯誤
if (oe instanceof FieldError) {
FieldError fe = (FieldError) oe;
key = fe.getField();// 獲取錯誤驗證字段名
} else {
// 非字段錯誤
key = oe.getObjectName();// 獲取驗證對象名稱
}
// 錯誤信息
msg = oe.getDefaultMessage();
errMap.put(key, msg);
}
return errMap;
}
測試
http://localhost:8080/user/valid/validate
{
id : null,
date : '2017-08-08',
doubleValue : 999999.09,
integer : 100,
range : 1000,
email : 'email',
size : 'adv1212',
regexp : 'a,b,c,d'
}
{
"date":"需要一個將來日期",
"size":"字符串長度要求20到30之間。",
"range":"範圍爲1至888",
"integer":"最大值爲88",
"doubleValue":"必須小於或等於10000.00",
"id":"id不能爲空",
"email":"郵箱格式錯誤"
}
- 驗證購買的總價格 = 單價 X 數量 ,驗證不了
參數驗證機制
-
參數轉換的時候,存在 WebDataBinder機制管理
-
Spring會自動的 根據上下文 通過轉換器 轉換出 控制器 所需的參數
-
還 允許註冊 驗證器 validator
-
還允許使用註解 @InitBinder ,作用是 允許在進入控制器方法前 修改 WebDataBinder機制
public interface Validator { //判定當前驗證器是否支持 class 類型 boolean supports(Class<?> var1); //如果supports返回true,則這個方法執行邏輯 void validate(@Nullable Object var1, Errors var2); }
自定義驗證器
public class UserValidator implements Validator {
// 該驗證器只是支持User類驗證
@Override
public boolean supports(Class<?> clazz) {
return clazz.equals(User.class);
}
// 驗證邏輯
@Override
public void validate(Object target, Errors errors) {
// 對象爲空
if (target == null) {
// 直接在參數處報錯,這樣就不能進入控制器的方法了
errors.rejectValue("", null, "用戶不能爲空");
return;
}
// 強制轉換
User user = (User) target;
// 用戶名非空串
if (StringUtils.isEmpty(user.getUserName())) {
// 增加錯誤,可以進入控制器方法
errors.rejectValue("userName", null, "用戶名不能爲空");
}
}
}
綁定
-
要綁定給 webDataBinder
-
@InitBinder
-
執行控制器方法前,處理器會先執行 被 @InitBinder標註的方法
-
/** * 調用控制器前先執行這個方法 * #控制器已經不需要 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date * @param binder */ @InitBinder public void initBinder(WebDataBinder binder) { // 綁定驗證器 binder.setValidator(new UserValidator()); // 定義日期參數格式,參數不再需註解@DateTimeFormat,boolean參數表示是否允許爲空 binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false)); }
-
/** * * @param user * -- 用戶對象用StringToUserConverter轉換 * @param Errors * --驗證器返回的錯誤 * @param date * -- 因爲WebDataBinder已經綁定了格式,所以不再需要註解 * @return 各類數據 */ @GetMapping("/validator") @ResponseBody public Map<String, Object> validator(@Valid User user, Errors Errors, Date date) { Map<String, Object> map = new HashMap<>(); map.put("user", user); map.put("date", date); // 判斷是否存在錯誤 if (Errors.hasErrors()) { // 獲取全部錯誤 List<ObjectError> oes = Errors.getAllErrors(); for (ObjectError oe : oes) { // 判定是否字段錯誤 if (oe instanceof FieldError) { // 字段錯誤 FieldError fe = (FieldError) oe; map.put(fe.getField(), fe.getDefaultMessage()); } else { // 對象錯誤 map.put(oe.getObjectName(), oe.getDefaultMessage()); } } } return map; }
-
Errors Errors 通過驗證器 驗證後 得到的錯誤信息
測試
{
"date": "2017-12-31T16:00:00.000+00:00",
"userName": "用戶名不能爲空",
"user": {
"id": 1,
"userName": "",
"note": "備註"
}
}
http://localhost:8080/user/validator?user=1--備註&date=2018-01-01
數據模型
-
綁定數據,爲 試圖渲染做準備
-
模型和視圖 modelAndView
- view object
- model modelMap
- status httpStatus
- cleared boolean
-
modelMap
- 繼承:linkedHashMap
- modelmap
- extendedModelMap
- bindingAwareModelMap
-
在控制器方法的參數中 使用ModelAndView Model或 ModelMap作爲參數
-
會自動創建數據模型對象
controller
@RequestMapping("/data")
@Controller
public class DataModelController {
// 注入用戶服務類
@Autowired
private UserService userService = null;
// 測試Model接口
@GetMapping("/model")
public String useModel(Long id, Model model) {
User user = userService.getUser(id);
model.addAttribute("user", user);
// 這裏返回字符串,在Spring MVC中,會自動創建ModelAndView且綁定名稱
return "data/user";
}
// 測試modelMap類
@GetMapping("/modelMap")
public ModelAndView useModelMap(Long id, ModelMap modelMap) {
User user = userService.getUser(id);
ModelAndView mv = new ModelAndView();
// 設置視圖名稱
mv.setViewName("data/user");
// 設置數據模型,此處modelMap並沒有和mv綁定,這步系統會自動處理
modelMap.put("user", user);
return mv;
}
// 測試ModelAndView
@GetMapping("/mav")
public ModelAndView useModelAndView(Long id, ModelAndView mv) {
User user = userService.getUser(id);
// 設置數據模型
mv.addObject("user", user);
// 設置視圖名稱
mv.setViewName("data/user");
return mv;
}
}
jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用戶信息</title>
</head>
<body>
<table>
<tr>
<td>編號</td>
<td>${user.id}</td>
</tr>
<tr>
<td>用戶名</td>
<td>${user.userName}</td>
</tr>
<tr>
<td>備註</td>
<td>${user.note}</td>
</tr>
</table>
</body>
</html>
視圖 和 視圖 解析器
-
是渲染 數據模型 展示給用戶的組件
-
邏輯視圖 和 非邏輯視圖
-
邏輯視圖需要視圖解析器 viewResolver (可用InternalResourceViewResolver)進一步定位,才能找到 視圖將數據模型 進行渲染
-
非邏輯視圖:不需要進一步定位,直接將數據模型渲染出來即可
-
還有 Excel PDF 等
視圖的設計
public interface View {
//響應狀態屬性
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
//路徑變量
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
//選擇內容類型
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
//響應類型:獲取 http響應類型,文本,json,文件
@Nullable
default String getContentType() {
return null;
}
//渲染方式。將數據模型 渲染到數據。model數據模型,
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
- pdf視圖
- AbstractPdfView
- Json視圖
- abstractJacksonView
- MappingJackson2JsonView
- Excel視圖
- abstractXlsView
- abstractXlsView
- abstractXlsxStreamingView
- 邏輯視圖
- abstractURLBasedView
- InternalResourceView
- InternalResourceView
- jstlView (Jsp視圖)
PDF視圖
-
AbstractPdfView 是非邏輯視圖,不需要視圖解析器去定位
protected abstract void buildPdfDocument( Map<String, Object> model, //數據模型 Document document, // PdfWriter writer, HttpServletRequest request, HttpServletResponse response ) throws Exception;
pom
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>core-renderer</artifactId>
<version>R8</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version> //或12版本
</dependency>
定義PDF導出的接口
public interface PdfExportService {
public void make(Map<String, Object> model, Document document,
PdfWriter writer, HttpServletRequest request,
HttpServletResponse response);
}
導出PDF視圖類
- 繼承 AbstractPdfView,調度 PdfExportService 的make方法
public class PdfView extends AbstractPdfView {
// 導出服務接口
private PdfExportService pdfExportService = null;
// 創建對象的時候載入導出服務接口
public PdfView(PdfExportService pdfExportService) {
this.pdfExportService = pdfExportService;
}
// 調用接口實現
@Override
protected void buildPdfDocument(Map<String, Object> model, Document document,
PdfWriter writer, HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 調用導出服務接口類
pdfExportService.make(model, document, writer, request, response);
}
}
用戶控制器 導出PDF數據
// 導出接口
@GetMapping("/export/pdf")
public ModelAndView exportPdf(String userName, String note) {
// 查詢用戶信息列表
List<User> userList = userService.findUsers(userName, note);
// 定義PDF視圖
View view = new PdfView(exportService());
ModelAndView mv = new ModelAndView();
// 設置視圖
mv.setView(view);
// 加入數據模型
mv.addObject("userList", userList);
return mv;
}
// 導出PDF自定義
@SuppressWarnings("unchecked")
private PdfExportService exportService() {
// 使用Lambda表達式定義自定義導出
return (model, document, writer, request, response) -> {
try {
// A4紙張
document.setPageSize(PageSize.A4);
// 標題
document.addTitle("用戶信息");
// 換行
document.add(new Chunk("\n"));
// 表格,3列
PdfPTable table = new PdfPTable(3);
// 單元格
PdfPCell cell = null;
// 字體,定義爲藍色加粗
Font f8 = new Font();
f8.setColor(Color.BLUE);
f8.setStyle(Font.BOLD);
// 標題
cell = new PdfPCell(new Paragraph("id", f8));
// 居中對齊
cell.setHorizontalAlignment(1);
// 將單元格加入表格
table.addCell(cell);
cell = new PdfPCell(new Paragraph("user_name", f8));
// 居中對齊
cell.setHorizontalAlignment(1);
table.addCell(cell);
cell = new PdfPCell(new Paragraph("note", f8));
cell.setHorizontalAlignment(1);
table.addCell(cell);
// 獲取數據模型中的用戶列表
List<User> userList = (List<User>) model.get("userList");
for (User user : userList) {
document.add(new Chunk("\n"));
cell = new PdfPCell(new Paragraph(user.getId() + ""));
table.addCell(cell);
cell = new PdfPCell(new Paragraph(user.getUserName()));
table.addCell(cell);
String note = user.getNote() == null ? "" : user.getNote();
cell = new PdfPCell(new Paragraph(note));
table.addCell(cell);
}
// 在文檔中加入表格
document.add(table);
} catch (DocumentException e) {
e.printStackTrace();
}
};
}
- 得到用戶列表
- 設置上PDF視圖
- 設置上數據模型
<dependency>
<groupId>bouncycastle</groupId>
<artifactId>bcmail-jdk14</artifactId>
<version>138</version>
</dependency> //最新版配合起來缺這個文件
- 訪問:http://localhost:8080/user/export/pdf 。啓動報錯,無關緊要
文件上傳
-
DispatcherServlet會使用適配器模式
-
將HTTPServletRequest接口對象轉換爲 Multipart HttpServletRequest
-
此接口擴展了 httpServletRequest接口的所有方法
-
MultipartRequest
- getFileNames
- getFile
- getFiles
- getFileMap
- getMultiFileMap
- getMultPart ContentType
-
Multipart HttpServletRequest 繼承 上面 和 HTTPServletRequest
- getRequestMethod
- getRequestHeaders
- getMultipartHeaders
-
Abstract MultipartHttpServlet Request 抽象類,繼承上面
- Default Multipart HttpservletRequest
- Standard Multipart HttpServlet Request
-
MVC 會將 HTTPServletRequest 轉成 Multipart HttpServlet Request
-
配置 Multipart HttpServlet Request 通過 MultipartResolver接口
-
CommonsMultipartResolver 需要依賴第三方包,漸漸被廢棄
-
StandardServlet Multipart Resolver
-
文件上傳配置
# 是否啓用 Spring MVC 多分部上傳功能
spring.servlet.multipart.enabled=false
# 將文件寫入磁盤,值可以使用的後綴 MB 或 KB 來表示 兆字節 或 字節大小
spring.servlet.multipart.file-size-threshold=0
# 中間這3個需要配置
# 指定默認上傳的文件夾
spring.servlet.multipart.location=e:/springboot
# 限制單個文件最大大小,這裏設置爲5M。
spring.servlet.multipart.max-file-size=5242880
# 限制所有文件最大大小,這裏設置爲20M
spring.servlet.multipart.max-request-size=20MB 不能寫MB,需要Long類型
#是否延遲 多部件 文件 請求的參數 和 文件的解析
spring.servlet.multipart.resolve-lazily=false
- 根據這些配置,自動生成:StandardServlet Multipart Resolver
- 可以使用 Servlet Api的part接口,或 MVC 的 MultipartFile 接口 (需要第三方包)
jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上傳</title>
</head>
<body>
<form method="post"
action="./part" enctype="multipart/form-data">
<input type="file" name="file" value="請選擇上傳的文件"/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
controller
@Controller
@RequestMapping("/file")
public class FileController {
/**
* 打開文件上傳請求頁面
* @return 指向JSP的字符串
*/
@GetMapping("/upload/page")
public String uploadPage() {
return "/file/upload";
}
// 使用HttpServletRequest作爲參數
//在調用控制器之前,DispatcherServlet 會 將其裝換爲 MultipartHTTPServletRequest 對象
@PostMapping("/upload/request")
@ResponseBody
public Map<String, Object> uploadRequest(HttpServletRequest request) {
boolean flag = false;
MultipartHttpServletRequest mreq = null;
// 強制轉換爲MultipartHttpServletRequest接口對象
if (request instanceof MultipartHttpServletRequest) {
mreq = (MultipartHttpServletRequest) request;
} else {
return dealResultMap(false, "上傳失敗");
}
// 獲取MultipartFile文件信息
MultipartFile mf = mreq.getFile("file");
// 獲取源文件名稱
String fileName = mf.getOriginalFilename();
File file = new File(fileName);
try {
// 保存文件
mf.transferTo(file);
} catch (Exception e) {
e.printStackTrace();
return dealResultMap(false, "上傳失敗");
}
return dealResultMap(true, "上傳成功");
}
// 使用Spring MVC的MultipartFile類作爲參數
@PostMapping("/upload/multipart")
@ResponseBody
public Map<String, Object> uploadMultipartFile(MultipartFile file) {
String fileName = file.getOriginalFilename();
File dest = new File(fileName);
try {
file.transferTo(dest);
} catch (Exception e) {
e.printStackTrace();
return dealResultMap(false, "上傳失敗");
}
return dealResultMap(true, "上傳成功");
}
//推薦
@PostMapping("/upload/part")
@ResponseBody
public Map<String, Object> uploadPart(Part file) {
// 獲取提交文件名稱
String fileName = file.getSubmittedFileName();
try {
// 寫入文件
file.write(fileName);
} catch (Exception e) {
e.printStackTrace();
return dealResultMap(false, "上傳失敗");
}
return dealResultMap(true, "上傳成功");
}
// 處理上傳文件結果
private Map<String, Object> dealResultMap(boolean success, String msg) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", success);
result.put("msg", msg);
return result;
}
}
- return “/file/upload”;
- 跳轉到:src/main/webapp/WEB-INF/jsp/file/upload.jsp
- action="./part"
- 跳轉到 @RequestMapping("/file") @PostMapping("/upload/part")
攔截器
-
請求來到 DispatcherServlet,會根據HandlerMapping的機制 處找到處理器
-
返回:HandlerExecutionChain (包含處理器和攔截器)
-
攔截器會對處理器進行攔截,(通過攔截器就可以增強處理器的功能)
-
所有的攔截器都是實現 HandlerInterceptor
public interface HandlerInterceptor { //處理器執行前方法 default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } //處理器執行後方法 default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } //處理完成後方法 default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
-
preHandler方法
- 返回true——處理器(包含控制器功能)——postHandle方法——視圖處理——afterCompletion方法——結束
- 返回false——結束
開發攔截器
public class Interceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
System.out.println("處理器前方法");
// 返回true,不會攔截後續的處理
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("處理器後方法");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("處理器完成方法");
}
}
註冊攔截器
@SpringBootApplication(scanBasePackages = "com.springboot.chapter10")
@MapperScan(basePackages = "com.springboot.chapter10", annotationClass = Mapper.class)
public class Chapter10Application implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(Chapter10Application.class, args);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 註冊攔截器到Spring MVC機制,然後它會返回一個攔截器註冊
InterceptorRegistration ir = registry.addInterceptor(new Interceptor1());
// 指定攔截匹配模式,限制攔截器攔截請求
ir.addPathPatterns("/interceptor/*");
}
}
測試
@Controller
@RequestMapping("/interceptor")
public class InterceptorController {
@GetMapping("/start")
public String start() {
System.out.println("執行處理器邏輯");
return "/welcome";
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>深入Spring MVC</title>
</head>
<body>
<h1>
<%
System.out.println("視圖渲染");
out.print("歡迎學習Spring Boot MVC章節\n");
%>
</h1>
</body>
</html>
啓動的時候,會註冊 攔截器(執行這裏的代碼)
訪問:http://localhost:8080/interceptor/start
處理器前方法 ——————————攔截器的 preHandle
執行處理器邏輯 ——————————action的邏輯
處理器後方法 ————————————攔截器的 postHandle
視圖渲染(JSP)中java代碼 ——————————jsp第一行代碼
視圖渲染22 (JSP)中java代碼 ——————————jsp第二行代碼
處理器完成方法 ————————————————————afterCompletion 處理器完成方法
afterCompletion 執行完畢後,才執行out.print
此時所有後臺打印完畢,前端會展示out.print 的輸出 歡迎學習Spring Boot MVC章節
preHandle return false;
則直接接觸,不執行action方法
如果preHandle 出錯,也是如此
多個攔截器的執行順序
public class MulitiInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
System.out.println("【" + this.getClass().getSimpleName()
+"】處理器前方法");
// 返回true,不會攔截後續的處理
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("【" + this.getClass().getSimpleName()
+"】處理器後方法");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("【" + this.getClass().getSimpleName()
+"】處理器完成方法");
}
}
處理器前方法
處理器前方法2
執行處理器邏輯
處理器後方法2
處理器後方法
視圖渲染(JSP)中java代碼
視圖渲染22 (JSP)中java代碼
處理器完成方法2
處理器完成方法
-
這是 責任鏈 模式的規則
-
先註冊 先執行,
-
而 處理器後 方法 和 完成方法 ,則是 先註冊 後執行
-
把 第二個攔截器 執行前方法返回false
-
處理器前方法 處理器前方法2 處理器完成方法
-
則後續的攔截器,處理器 和 所有攔截器的處理器後 postHandler 方法都不執行
-
完成方法 只會執行已經返回true的攔截器 的 完成方法,而順序是 先註冊 後執行
-
國際化
- 國際化消息源機制,MessageSource接口體系
- 大部分情況使用JDK的 ResourceBundle處理國際化消息,主要使用 ResourceBundleMessageSource
配置
# 指定國際化區域,可以覆蓋"Accept-Language" 頭信息
#spring.mvc.locale=
#國際化解析器,可以選擇:fixed、accept-header
#fixed代表固定的國際化,accept-header代表讀取瀏覽器的"Accept-Language"頭信息
#spring.mvc.locale-resolver=accept-header
# 文件編碼
spring.messages.encoding=UTF-8
# 國際化文件基礎名稱
spring.messages.basename=international
# 國際化消息緩存有效時間(單位秒),超時將重新載入
spring.messages.cache-duration=3600
Spring MVC 拾遺
@ResponseBody轉爲 JSON
-
當執行完控制器 返回,處理器會 啓用結果 解析器(Result Resolver )
-
去輪詢 註冊給Spring MVC 的 HttpMessageConverter接口的實現類
-
因爲:MappingJackson2HttpMessageConverter這個實現類 已經被Spring MVC 所註冊
-
Spring MVC將控制器的結果類型 標明爲 JSON
-
標註@ResponseBody——執行控制器返回——輪詢註冊的HttpMessageConverter (通過MappingJackson2HttpMessageConverter的canWriter方法確定是否轉換結果)
-
是
- 轉換爲JSON數據集 結束
-
否
- 後續視圖流程
重定向
- redirect
<insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
insert into t_user(user_name, note) values(#{userName}, #{note})
</insert>
// 顯示用戶
@GetMapping("/show")
public String showUser(Long id, Model model) {
User user = userService.getUser(id);
model.addAttribute("user", user);
return "data/user";
}
// 使用字符串指定跳轉
@GetMapping("/redirect1")
public String redirect1(String userName, String note) {
User user = new User();
user.setNote(note);
user.setUserName(userName);
// 插入數據庫後,回填user的id
userService.insertUser(user);
return "redirect:/user/show?id=" + user.getId();
}
// 使用模型和視圖指定跳轉
@GetMapping("/redirect2")
public ModelAndView redirect2(String userName, String note) {
User user = new User();
user.setNote(note);
user.setUserName(userName);
userService.insertUser(user);
ModelAndView mv = new ModelAndView();
mv.setViewName("redirect:/user/show?id=" + user.getId());
return mv;
}
<body>
<table>
<tr>
<td>編號</td>
<td>${user.id}</td>
</tr>
<tr>
<td>用戶名</td>
<td>${user.userName}</td>
</tr>
<tr>
<td>備註</td>
<td>${user.note}</td>
</tr>
</table>
</body>
http://localhost:8080/user/redirect1?userName=zhangsan¬e=%E5%A4%87%E6%B3%A82
會調到第一個action,ID 爲插入返回的ID
http://localhost:8080/user/show?id=3
重定向優化
// 顯示用戶
// 參數user直接從數據模型RedirectAttributes對象中取出
@RequestMapping("/showUser")
public String showUser(User user, Model model) {
System.out.println(user.getId());
return "data/user";
}
// 使用字符串指定跳轉
@RequestMapping("/redirect1")
public String redirect1(String userName, String note, RedirectAttributes ra) {
User user = new User();
user.setNote(note);
user.setUserName(userName);
userService.insertUser(user);
// 保存需要傳遞給重定向的對象
ra.addFlashAttribute("user", user);
return "redirect:/user/showUser";
}
// 使用模型和視圖指定跳轉
@RequestMapping("/redirect2")
public ModelAndView redirect2(String userName, String note,
RedirectAttributes ra) {
User user = new User();
user.setNote(note);
user.setUserName(userName);
userService.insertUser(user);
// 保存需要傳遞給重定向的對象
ra.addFlashAttribute("user", user);
ModelAndView mv = new ModelAndView();
mv.setViewName("redirect:/user/showUser");
return mv;
}
- 第一次在 showUser方法 ,要重新查一次
- 第二次 使用 RedirectAttributes ra ; ra.addFlashAttribute(“user”, user);
- /showUser 無需查詢放入model了
操作會話對象
-
操作 httpSession 十分普遍
-
@SessionAttribute 用於參數,將httpSession屬性讀出,賦予控制器
-
@SessionAttributes 用於類,數據模型的屬性保存到session中
-
setjsp 和test.jsp
<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>用戶詳情</title>
</head>
<body>
<%
session.setAttribute("id",1L);
response.sendRedirect("./test");
%>
</body>
</html>
查看的頁面
<%@ page import="com.hua.testj.pojo.User" %>
<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>用戶詳情</title>
</head>
<body>
<%
User user = (User) session.getAttribute("user");
Long id_new = (Long)session.getAttribute("id_new");
out.print(user.getUserName());
out.println(id_new);
%>
</body>
</html>
//指定數據模型名稱 或 屬性類型,保存到 session中
@SessionAttributes(names = {"user"},types = Long.class)
@Controller
@RequestMapping("/session")
public class SessionController {
@Autowired
private UserService userService=null;
//先訪問這個跳轉
@GetMapping("/setjsp")
public ModelAndView myTest(){
ModelAndView mv = new ModelAndView();
// 定義模型視圖
mv.setViewName("session/setjsp");
// 返回模型和視圖
return mv;
}
@GetMapping("/test")
public String test(@SessionAttribute("id") Long id, Model model){
model.addAttribute("id_new",id);
User user=userService.getUser(id);
model.addAttribute("user",user);
return "session/test";
}
}
給控制器增加通知
-
在控制器方法的前後 和 異常 發生時 去執行不同的處理
-
@ControllerAdvice:控制器的通知類,增強控制器的各類通知,和限定增強哪些控制器 功能
-
@InitBinder:控制器參數綁定規則,轉換規則,格式化,會在參數轉換之前執行
-
@ExceptionHandler:發生異常後的操作。如跳轉到指定的頁面
-
@ModelAttribute:在控制器方法執行之前,對數據模型進行操作
-
一個工具包
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.9.4</version>
</dependency>
通知
@ControllerAdvice(
// 指定攔截的包
basePackages = { "com.springboot.chapter10.controller.advice.test.*" },
// 限定被標註爲@Controller的類才被攔截
annotations = Controller.class)
public class MyControllerAdvice {
// 綁定格式化、參數轉換規則和增加驗證器等 2
@InitBinder
public void initDataBinder(WebDataBinder binder) {
// 自定義日期編輯器,限定格式爲yyyy-MM-dd,且參數不允許爲空
CustomDateEditor dateEditor =
new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false);
// 註冊自定義日期編輯器
binder.registerCustomEditor(Date.class, dateEditor);
}
// 在執行控制器之前先執行,可以初始化數據模型。 1
@ModelAttribute
public void projectModel(Model model) {
model.addAttribute("project_name", "chapter10");
}
//拋出了異常,就執行下面代碼
// 異常處理,使得被攔截的控制器方法發生異常時,都能用相同的視圖響應
@ExceptionHandler(value = Exception.class)
public String exception(Model model, Exception ex) {
// 給數據模型增加異常消息
model.addAttribute("exception_message", ex.getMessage());
// 返回異常視圖
return "exception";
}
}
controller
@Controller
@RequestMapping("/advice")
public class AdviceController {
@GetMapping("/test")
// 因爲日期格式被控制器通知限定,所以無法再給出
public String test(Date date, ModelMap modelMap) {
// 從數據模型中獲取數據
System.out.println(modelMap.get("project_name"));
// 打印日期參數
System.out.println(DateUtils.format(date, "yyyy-MM-dd"));
// 拋出異常,這樣流轉到控制器異常通知
throw new RuntimeException("異常了,跳轉到控制器通知的異常信息裏");
}
}
exception.jsp
http://localhost:8080/advice/test
<h3><td>${exception_message}</td></h3>
獲取請求頭
-
利用請求頭的數據 進行身份的驗證
-
@RequestHeader
-
<%@ page pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>獲取請求頭參數</title> <!-- 加載Query文件--> <script src="https://code.jquery.com/jquery-3.2.0.js"> </script> <script type="text/javascript"> $.post({ url : "./user", // 設置請求頭參數 headers : {id : '1'}, // 成功後的方法 success : function(user) { if (user == null || user.id == null) { alert("獲取失敗"); return; } // 彈出請求返回的用戶信息 alert("id=" + user.id +", user_name=" +user.userName+", note="+ user.note); } }); </script> </head> <body> </body>
-
@GetMapping("/header/page") public String headerPage() { return "header"; } @PostMapping("/header/user") @ResponseBody // 通過@RequestHeader接收請求頭參數 public User headerUser(@RequestHeader("id") Long id) { User user = userService.getUser(id); return user; }
-
http://localhost:8080/user/header/page