深入淺出9,10章 Spring MVC

SpringMVC 介紹

  • 視圖 view
  • 數據模型 model
  • 視圖解析器 viewResolver
  • 處理適配器 HandlerAdapter

設計理念

  • model — view—controller

  • http請求 ——控制器—— 業務層

  • 控制器——相應請求——視圖層

  • 業務層(包含nosql緩存)——數據訪問層(關係數據庫,包含事務)

流程

  • 圍繞DispatcherServlet
  • 加入@ResponseBody是 沒有經過視圖解析器和視圖渲染的
  1. http Request——dispatcher servlet

  2. 處理器映射:定位控制器 相應方法(handlerMapping)

    1. 處理器執行鏈:包含處理器 及其攔截器(handlerExecutionChain)
    2. dispatcher servlet
  3. 處理適配器:運行處理器 (HandlerAdapter)

  4. 模型與視圖:獲取視圖和模型數據 (Model And View)——dispatcher servlet

  5. 視圖解析器:定位視圖資源 (viewResolver)

  6. 視圖:將數據模型渲染展示(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

    1. WebDataBinder
    • converter 普通轉換器 integer,從http得到字符串轉成integer
    • formatter 格式化轉換器,日期
    • genericConverter http參數 轉換成數組
    1. Java Pojo
    2. 驗證機制
    3. 調用控制器
  • 數據類型的轉換,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&note=%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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章