類型轉換器
<!--注解驅動,配置通路靜态資源-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--配置自定義類型轉換器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.demo.converters.MyStringToDateConverter" />
</set>
</property>
</bean>
package com.demo.converters;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 定義類型轉換器 需要明确 源類型 和 目标類型
* 在convert方法自定義類型轉換的實作
* 在spring配置檔案中配置自定義類型轉換器
*/
public class MyStringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
try {
if (!StringUtils.isEmpty(source)) {
if (source.split("-").length == 3) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(source);
} else if(source.split("/").length == 3) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
return sdf.parse(source);
} else {
throw new RuntimeException("日期轉換錯誤:" + source);
}
}
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
資料格式化
- @NumberFormat ===> 用在javabean的屬性或方法參數上
- @DateTimeFormat ===> 用在javabean的屬性或方法參數上
<%--spring标簽庫--%>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>
...
<%--spring:eval 用來顯示格式化後的資料--%>
salary: <spring:eval expression="user.salary"></spring:eval>
package com.demo.controllers.entity;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import java.util.Date;
@Data
public class User {
private Integer id;
private String username;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday; // 2023-03-03
@NumberFormat(style = NumberFormat.Style.CURRENCY)
private Double balance; // ¥1000
private String[] hobbies;
@NumberFormat(pattern = "#,###.##")
private Double salary; // 1,000.01
@NumberFormat(style = NumberFormat.Style.PERCENT)
private Double taskCount; // 96%
}
資料校驗
Hibernate Validator實作了JSR349驗證注解規範的技術,在javaBean屬性上标注
- 基于原生html form表單實作方式
<form action="${pageContext.request.contextPath}/user" method="post">
id:<input name="id" type="text" /> ${errors.id}<br />
<input type="submit" value="送出" />
</form>
package com.demo.controllers.entity;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class User {
@NotNull
@Min(1)
private Integer id;
}
package com.demo.controllers;
import com.demo.controllers.entity.User;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 在需要驗證的javaBean的屬性上面加入對應的驗證注解
* 在需要驗證的處理方法的對應javaBean參數上加上@Valid
* 在需要驗證的處理方法參數中加入BindingResult,代表自己處理錯誤,這樣就不會顯示錯誤頁面了
* 将錯誤資訊循環通過map存入到request域中
* 在jsp通過${errors.id}擷取對應的錯誤資訊
*/
@Controller
public class UserController {
@PostMapping("/user")
public String add(@Valid User user, BindingResult result, Model model) {
// 将錯誤資訊取出來,輸出到jsp頁面
// 判斷目前是否出現了錯誤
if(result.hasErrors()) {
// 存放錯誤資訊,有利于在jsp中分别取出錯誤資訊
Map<String, String> errors = new HashMap<>(); // key: 錯誤資訊的屬性名,value: 錯誤資訊
// 擷取所有的錯誤資訊 包含 錯誤的屬性,錯誤資訊
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
}
model.addAttribute("errors", errors);
//如果驗證失敗将請求轉發到添加頁面
return "user/add";
}
System.out.println(user);
return "show";
}
}
- 基于Spring form标簽庫的實作方式(靜态資料)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>添加使用者</title>
</head>
<body>
<h1>添加使用者</h1>
<%-- Spring的form标簽庫method是支援put和delete--%>
<form:form action="${pageContext.request.contextPath}/spring/user" method="post" modelAttribute="user">
id: <form:input path="id"></form:input><form:errors path="id"></form:errors>
</form:form>
</body>
</html>
/**
* 在jsp中導入spring-form标簽庫
* 在form标簽上一定要加上modelAttribute
* 加上對應的form标簽,必須都要以<form:開頭
* @param user
* @param result
* @param model
* @return
*/
@PostMapping("/form/user")
public String springformAdd(@Valid User user, BindingResult result, Model model) {
if (result.hasErrors()) {
return "user/add";
}
System.out.println(user);
return "show";
}
/**
* 添加一個顯示jsp的處理方法,一定要傳入一個空的User到model中
* @param user
* @return
*/
@GetMapping("/user/add")
public String addView(User user) {
return "user/add";
}
- 基于Spring form标簽庫的實作方式(動态資料)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>添加使用者</title>
</head>
<body>
<h1>添加使用者</h1>
<form:form action="${pageContext.request.contextPath}/spring/user" method="post" modelAttribute="user">
hobbies:
<%--靜态資料源--%>
<form:checkbox path="hobbies" value="唱歌"></form:checkbox>
<form:label path="hobbies">唱歌</form:label>
<form:checkbox path="hobbies" value="跳舞"></form:checkbox>
<form:label path="hobbies">跳舞</form:label> <br>
<%--動态資料源--%>
<form:checkboxes path="hobbies" items="${list}"></form:checkboxes>
<select>
<option value="1">北京</option>
<option value="2">上海</option>
</select>
</form:form>
</body>
</html>
/**
* 初始化頁面的資料
* 調用其它的方法之前,都要調用此方法
*/
@ModelAttribute
public void initData(Model model) {
// 初始化資料
List<String> list = Arrays.asList("唱歌", "跳舞");
model.addAttribute("list", list);
// Map<String, String> map = new HashMap<>();
// map.put("1", "唱歌");
// map.put("2", "跳舞");
//
// model.addAttribute("list", map);
}
/**
* 在jsp中導入spring-form标簽庫
* 在form标簽上一定要加上modelAttribute
* 加上對應的form标簽,必須都要以<form:開頭
* @param user
* @param result
* @return
*/
@PostMapping("/form/user")
public String springformAdd(@Valid User user, BindingResult result) {
if (result.hasErrors()) {
return "user/add";
}
System.out.println(user);
return "show";
}
/**
* 添加一個顯示jsp的處理方法,一定要傳入一個空的User到model中
* @param user
* @return
*/
@GetMapping("/user/add")
public String addView(User user) {
return "user/add";
}
JSON處理
java轉換為json的過程:序列化
json轉換為java的過程:反序列化
json要用雙引号,不能用單引号
JAVA | JSON |
String | "string" |
Integer | 111 |
JavaBean/Map | {"id":1, "name":"zhangsan"} |
數組/集合 String[]/List<String> | ["a","b","c"] |
List<User> List<Map> | [ {"id":1, "name":"zhangsan"}, {"id":2, "name":"wangwu"}, ... ] |
User: 屬性:id,name, Role role | {"id":1, "name":"zhangsan","role":{"id":2, "name":"wangwu"}} |
User: 屬性:id,name,List<Role> | {"id":1, "name":"zhangsan","role":[{"id":2, "name":"wangwu"}, {"id":3, "name":"lisi"} ,...] } |
- RestController
控制器類中所有的處理方法都會以json的資料進行響應,相當于控制器類中所有的處理方法都加上了@ResponseBody
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.14.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.14.2</version>
</dependency>
package com.demo.controllers.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String usernae;
@JsonIgnore // 當傳回javaBean的json時,會忽略該屬性
private String password;
@JsonFormat(pattern = "yyyy-MM-dd") // 使用者轉換json時格式化資料
private Date birthday;
}
package com.demo.controllers;
import com.demo.controllers.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@Controller
@RestController
public class JsonController {
@RequestMapping("/json/response")
// @ResponseBody // 将傳回值作為文本進行傳回,并不是傳回視圖
public User responseJson() {
User user = new User(1, "zhangsan", "123456", new Date());
return user;
}
}
- Spring MVC擷取JSON資料
// 對象轉化為json字元串
var jsonValue = JSON.stringify(user);
- @RequestBody: 請求中JSON資料轉化成JAVA
- @ResponseBody: 響應中把JAVA轉化成JSON資料
Spring MVC上傳|下載下傳
- Servlet 下載下傳
package com.demo.controllers;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
/**
* Servlet下實作的檔案下載下傳
*/
@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String realPath = this.getServletContext().getRealPath("/file/1.png"); // 擷取要下載下傳的檔案的絕對路徑
// 将檔案路徑封裝成File對象
File tmpFile = new File(realPath);
// 擷取檔案名
String fileName = tmpFile.getName();
// 設定響應頭 content-disposition: 設定檔案下載下傳的打開方式,預設在網頁上打開
// attachment;filename是以下載下傳方式來打開檔案
resp.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
InputStream is = new FileInputStream(realPath); // 擷取檔案輸入流
int len = 0;
byte[] buffer = new byte[1024];
OutputStream out = resp.getOutputStream();
while((len = is.read(buffer)) != -1) {
out.write(buffer, 0, len); // 将緩沖區的資料輸出到用戶端浏覽器
}
is.close();
}
}
- Spring MVC 下載下傳
package com.demo.controllers;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
@Controller
public class DownloadController {
@RequestMapping("/download1")
public String download1(HttpServletRequest request, HttpServletResponse response) throws Exception{
String realPath = request.getServletContext().getRealPath("F:\\code\\springmv01\\web\\images\\img.png");
File tmpFile = new File(realPath);
String fileName = tmpFile.getName();
response.setHeader("content-disposition", "attachment; filename = " + URLEncoder.encode(fileName, "UTF-8"));
InputStream is = new FileInputStream(realPath);
int len = 0;
byte[] buffer = new byte[1024];
OutputStream out = response.getOutputStream();
while((len = is.read(buffer)) != -1) {
out.write(buffer, 0, len); // 将緩沖區的資料輸出到用戶端浏覽器
}
is.close();
return null;
}
/**
* 基于Spring ResponseEntity的檔案下載下傳 不支援緩沖區
* ResponseEntity 可以定制檔案的響應内容,響應頭,響應狀态碼
* @return
*/
@RequestMapping("/getDownload")
public ResponseEntity<String> getDownload() {
String body = "hello world";
HttpHeaders headers = new HttpHeaders();
headers.set("Set-Cookie","name=hello");
return new ResponseEntity<>(body, headers, HttpStatus.OK);
}
@RequestMapping("/download2")
public ResponseEntity<Byte[]> download2(HttpServletRequest request, HttpServletResponse response) throws Exception{
String realPath = request.getServletContext().getRealPath("F:\\code\\springmv01\\web\\images\\img.png");
File tmpFile = new File(realPath);
String fileName = tmpFile.getName();
HttpHeaders headers = new HttpHeaders();
headers.set("content-disposition", "attachment; filename = " + URLEncoder.encode(fileName, "UTF-8"));
InputStream is = new FileInputStream(realPath);
return new ResponseEntity<Byte[]>(new Byte[is.available()], headers, HttpStatus.OK);
}
}
- Spring MVC 上傳
<!--檔案上傳解析器-->
<bean id= "multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
<!--檔案上傳最大的位元組數-->
<property name="maxUploadSize" value="#{1024*1024*10}" />
</bean>
<form enctype="multipart/form-data" action="${pageContext.request.contextPath}/upload" method="post">
檔案描述:<input type="text" name="desc" /> <br>
檔案:<input type="file" name="myfile" multiple accept="image/*"/> <br> <%--multiple: 多檔案上傳--%>
<input type="submit" value="送出" />
</form>
/**
* 多檔案檔案上傳
*/
@PostMapping("/upload")
public String upload(String desc, MultipartFile[] multipartFiles) throws IOException {
for (MultipartFile myfile : multipartFiles) {
String path = "D:/" + myfile.getOriginalFilename();
File file = new File(path);
myfile.transferTo(file);
}
return "success";
}
/**
* 多線程檔案上傳
*/
@PostMapping("/upload")
public String upload(String desc, MultipartFile[] multipartFiles) throws InterruptedException {
for (MultipartFile myfile : multipartFiles) {
Thread thread = new Thread(() -> {
String path = "D:/" + myfile.getOriginalFilename();
File file = new File(path);
try {
myfile.transferTo(file);
} catch (IOException e) {
e.printStackTrace();
}
});
thread.start();
thread.join(); // 讓子線程執行完在執行主線程
}
return "success";
}
- Spring MVC虛拟目錄上傳
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--磁盤路徑--%>
<img src="${pageContext.request.contextPath}/img/${filename}">
</body>
</html>
/**
* 檔案上傳到三個路徑
* 1. 項目路徑(适合項目小,上傳使用率低
* 2. 磁盤路徑,這種方式通過虛拟目錄的映射
* 3. 靜态資源伺服器(CDN)
*/
@PostMapping("/upload")
public String upload(String desc, MultipartFile myfile, Model model) throws IOException {
String path = "D:/" + myfile.getOriginalFilename();
File file = new File(path);
myfile.transferTo(file);
model.addAttribute("filename", myfile.getOriginalFilename());
return "success";
}
Spring MVC攔截器
攔截器:采用AOP的設計思想,類似Servlet過濾器,用來攔截處理方法
如:權限驗證、日志、異常記錄、記錄方法執行時間
<mvc:interceptors>
<bean class="com.demo.interceptors.MyInterceptor" />
</mvc:interceptors>
package com.demo.interceptors;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
public class MyInterceptor implements HandlerInterceptor {
/**
* 在處理方法之前執行
* @param request 在方法請求進來之前更改request中的的屬性值
* @param response
* @param handler 封裝了目前處理方法的資訊
* @return true: 後續調用鍊是否執行 false:中斷後續執行
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handler1 = (HandlerMethod) handler;
}
System.out.println("-------類:["+ handler1.getBean().getClass() +"] 方法:["+ handler1.getMethod().getName() +"] 參數:["+ Arrays.toString(handler1.getMethod().getParameters()) +"] 前執行---------preHandle--------------------------------");
return true;
}
/**
* 在請求執行後執行,在視圖渲染之前執行
* 當處理方法出現了異常則不會執行方法
* @param request
* @param response 在方法執行後更改response中的資訊
* @param handler 封裝了目前處理方法的資訊
* @param modelAndView 封裝了model和view,請求結束後可以修改model中的資料或新增model資料,也可修改view的跳轉
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("------方法後執行,在視圖渲染之前----------postHandle--------------------------------");
}
/**
* 在視圖渲染之後執行,出現異常也會執行該方法
* @param request
* @param response
* @param handler
* @param ex 可以記錄異常日志的功能,或者清除資源
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("-----------在視圖渲染之後--------afterHandle----------");
}
}
- 攔截器|過濾器的差別
<!--必須保證對應的請求沒有映射處理-->
<mvc:view-controller path="/admin" view-name="admin"></mvc:view-controller>
<mvc:interceptors>
<!--直接配置bean會攔截Spring MVC所有請求-->
<bean class="com.demo.interceptors.MyInterceptor" />
<!--不是所有的請求都要攔截-->
<mvc:interceptor>
<!--需要攔截請求-->
<mvc:mapping path="/**"/>
<!--不需要攔截的請求-->
<mvc:exclude-mapping path="/login"/>
<!--攔截器-->
<bean class="com.demo.interceptors.CheckLoginInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
package com.demo.interceptors;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 驗證使用者是否登入的攔截器
*/
public class CheckLoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
// 如果沒有登入
if (StringUtils.isEmpty(session.getAttribute("username"))) {
response.sendRedirect(request.getContextPath() + "/login");
return false;
} else {
return true;
}
}
}
Spring MVC 國際化
- 通過浏覽器語言設定國際化
<!--設定國際化支援 配置國際化屬性資源檔案-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename">
<array>
<value>i18n/login</value>
<value>i18n/index</value>
</array>
</property>
</bean>
package com.demo.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 基于浏覽器設定的語言切換國際化
* 1. 建立jsp對應的國際化屬性資源文
* login_en_US.properties
* login_zh_CN.properties
* 2. 配置sping_mvc.xml 将國際化支援和資源檔案都注入到springmvc中
* 3. 在jsp頁面調用對應的屬性資源内容:<spring:message code="綁定屬性資源檔案中對應的key"
*/
@Controller
public class I8NController {
@RequestMapping("/i18n")
public String i18n() {
return "login";
}
}
- 通過超連結來切換國際化
》》》方式一
<!--使用SessionLocaleResolver保持Locale的狀态,會在session中擷取Locale對象-->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>
package com.demo.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
/**
* 基于浏覽器設定的語言切換國際化
* 1. 建立jsp對應的國際化屬性資源文
* login_en_US.properties
* login_zh_CN.properties
* 2. 配置sping_mvc.xml 将國際化支援和資源檔案都注入到springmvc中
* 3. 在jsp頁面調用對應的屬性資源内容:<spring:message code="綁定屬性資源檔案中對應的key"
*/
@Controller
public class I8NController {
@RequestMapping("/i18n")
public String i18n() {
return "login";
}
@RequestMapping("/i18n/{language}_{country}")
public String changeLocale(@PathVariable("language") String language,
@PathVariable("country") String country,
HttpServletRequest request, HttpServletResponse response,
@Autowired SessionLocaleResolver localeResolver) {
Locale local = new Locale(language, country);
localeResolver.setLocale(request, response, local);
return "login";
}
}
》》》方式二
<!--使用SessionLocaleResolver保持Locale的狀态,會在session中擷取Locale對象-->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>
<!--使用springmvc提供的攔截器,接收local參數,設定到session中去-->
<mvc:interceptors>
<!--所有的請求都會被攔截-->
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
</mvc:interceptors>
- Spring MVC基于注解的國際化
Spring MVC 異常處理
- @ExceptionHandler
通過@ExceptionHandler可以在方法中記錄日志
- @ControllerAdvice是對Controller的增強
全局異常處理
全局資料綁定
全局資料預處理
》》》優先級:處理器異常 > 全局異常中具體異常 > 全局異常