类型转换器
<!--注解驱动,配置访问静态资源-->
<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的增强
全局异常处理
全局数据绑定
全局数据预处理
》》》优先级:处理器异常 > 全局异常中具体异常 > 全局异常