1. 参数转换
概念: 处理器适配器中可配置自定义类型转换器,用于将请求参数类型转成自定义类型:
- 开发转换器类:实现
o.s.c.c.c.Converter<A, B>接口并重写convert():- 该转换器仅在动作方法使用B类型变量接收A类型请求参数时生效。
- 配置转换器类:主配文件中管理
o.s.f.s.FormattingConversionServiceFactoryBean:- 注入
converters:使用<set>+<bean>注入1或N个转换器类全名。 - 使用
<mvc:annotation-driven />的conversion-service引用转换器类。
- 注入
- 使用转换注解:主配文件中管理
o.s.f.s.FormattingConversionServiceFactoryBean:- 无需注入
converters,否则转换注解失效,仍使用转换器类方式。 - 使用
@DateTimeFormat(pattern="")标记动作方法参数或实体类属性。 - 异常信息存放在
BindingResult参数中,其位置必须紧跟爆发异常的那个实体类参数。
- 无需注入
源码: /springmvc4/
- res:
spring/springmvc.xml
<mvc:annotation-driven />
<bean id="converter-list"class="org.springframework.format.support.FormattingConversionServiceFactoryBean"/>
<!--配置自定义转换器类-->
<!--
<mvc:annotation-driven conversion-service="converter-list"/>
<bean id="converter-list" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.yap.converter.StringToDateConverter"/>
</set>
</property>
</bean>
-->
- src:
c.y.converter.StringToDateConverter
package com.yap.converter;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author yap
*/
public class StringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
System.out.println("convert()..." + source);
Date date = null;
try {
date = new SimpleDateFormat("yyyy-MM-dd").parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
- src:
c.y.controller.ConverterController
package com.yap.controller;
import com.yap.pojo.Student;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
/**
* @author yap
*/
@RequestMapping("/api/converter")
@Controller
public class ConverterController {
@ResponseBody
@RequestMapping("string-to-date")
public String stringToDate(Date date, String name) {
System.out.println(date);
System.out.println(name);
return "success";
}
@ResponseBody
@RequestMapping("date-time-format")
public String dateTimeFormat(Student student, BindingResult result) {
if (result.hasErrors()) {
System.out.println("爆发了" + result.getErrorCount() + "个异常!");
for (ObjectError e : result.getAllErrors()) {
System.out.println("爆发异常的对象是:" + e.getObjectName());
System.out.println("具体异常的内容为:" + e.getDefaultMessage());
}
}
System.out.println(student);
return "success";
}
}
- src:
c.y.pojo.Student
package com.yap.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
* @author yap
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
@JsonIgnore
private static final long serialVersionUID = 1L;
@JsonProperty("primary-key")
private Integer id;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String name;
<!--DateTimeFormat注解测试转换器类-->
@DateTimeFormat(pattern="MM-dd yyyy")
@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss", locale="zh", timezone="UTC")
private Date birthday;
}
- psm:
api/converter/string-to-date - psm:
api/converter/date-time-format
2. 参数校验
概念: HibernateValidator是JSR303的拓展校验工具,用于校验请求参数是否满足指定格式:
- 添加依赖:
hibernate-validator。 - 主配中添加
<mvc:annotation-driven />以驱动o.s.v.b.LocalValidatorFactoryBean校验类。 - 开发实体类:为属性标记HibernateValidator校验注解,多注解优先级从上到下:
@Null/@NotNull:属性必须为/不为null。@AssertTrue/@AssertFalse:属性必须为true/false。@Min(18)/@Max(18):属性必须是最小/大为18的整数。@DecimalMin(3.5)/@DecimalMax(3.5):属性必须是最小/大为3.5的浮点数。@Size(2, 9):属性的必须是2到9之间的一个数。@Past/@Future:属性必须是一个过去/将来的日期。@Pattern(value):属性必须符合指定的正则表达式。@Email:属性必须是合法的电子邮箱地址,JSR303拓展注解。@Length:字符串属性的长度必须在指定的范围内,JSR303拓展注解。@NotEmpty:字符串/集合/数组属性必须不为null且长度不为0,JSR303拓展注解。@Range:属性值必须在指定的范围内,JSR303拓展注解。
- 开发动作类:对动作方法实体类参数标记
@Valid以对其启用HibernateValidator校验。- 异常信息存放在
BindingResult参数中,其位置必须紧跟爆发异常的那个实体类参数。
- 异常信息存放在
- psm:
api/valid/hibernate-validator源码: /springmvc4/ - res:
pom.xml
<!--hibernate-validator-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
- src:
c.y.pojo.Teacher
package com.yap.pojo;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* @author yap
*/
@Data
public class Teacher implements Serializable {
@Null
private Integer id;
@NotNull
@Email
private String email;
@NotNull
@Past
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birth;
@NotEmpty
private List list;
@Length
private String a ;
}
- src:
c.y.controller.HibernateValidatorController
package com.yap.controller;
import com.yap.pojo.Teacher;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.validation.Valid;
/**
* @Author Yap
*/
@RequestMapping("/api/valid/")
@Controller
public class HibernateValidatorController {
@ResponseBody
@RequestMapping("hibernate-validator")
public String hibernateValid(@Valid Teacher teacher, BindingResult result) {
if (result.hasErrors()) {
System.out.println("爆发了" + result.getErrorCount() + "个异常!");
for (ObjectError e : result.getAllErrors()) {
System.out.println("爆发异常的对象是:" + e.getObjectName());
System.out.println("具体异常的内容为:" + e.getDefaultMessage());
}
}
System.out.println(teacher);
return "ok";
}
}
3. 文件上传
流程: 添加依赖 commons-fileupload:
- 主配中管理
o.s.w.m.c.CommonsMultipartResolver,id固定为multipartResolver:- 注入
maxUploadSize以配置上传文件大小限制,单位字节,默认-1无限制。 - 注入
maxInMemorySize以配置内存最大值使用容量,单位字节。 - 注入
defaultEncoding以配置文件编码。
- 注入
- 动作方法使用
@RequestParam("file") MultipartFile/MultipartFile[]接收流文件:file.getOriginalFilename():获取文件客户端真实名字。file.transferTo(File desc):将文件对象上传到指定位置。
- 视图层表单添加
enctype=multipart/form-data属性并以POST方式提交。 - psm:
api/file/upload:在Body/form data中KEY框末尾选择File并POST方式提交。
源码: /springmvc4/
- res:
pom.xml
<!--commons-fileupload-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
- res:
spring/springmvc.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1073741824"/>
<property name="maxInMemorySize" value="40960"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>
- src:
c.y.controller.FileController
package com.yap.controller;
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.IOException;
/**
* @author yap
*/
@Controller
@RequestMapping("/api/file")
public class FileController {
@ResponseBody
@RequestMapping("upload")
public String upload(@NotNull @RequestParam("avatar") MultipartFile avatar) throws IOException {
File desc = new File("F:\\upload" + System.currentTimeMillis() + avatar.getOriginalFilename());
if (!desc.getParentFile().exists()) {
System.out.println(desc.getParentFile().mkdirs());
}
avatar.transferTo(desc);
return "success";
}
}
- web:
view/upload.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>upload</title>
<base href="/springmvc4/">
</head>
<body>
<section>
<form action="api/file/upload" method="post" enctype="multipart/form-data">
<label><input type="file" name="avatar"></label>
<button type="submit">上传头像</button>
</form>
</section>
</body>
</html>
4. 文件下载
流程: 文件下载动作方法需要 new ResponseEntity<byte[]>(字节数组,响应头,http状态码) 并返回:
FileUtils.readFileToByteArray(file):获取文件字节数组。new HttpHeaders():获取响应头对象。setContentDispositionFormData("attachment", filename):设置以附件形式下载文件及附件名。setContentType(MediaType.APPLICATION_OCTET_STREAM):设置响应类型为8进制字节流。
HttpStatus.CREATED:http状态码201表示请求已成功,且创建了一个新资源并响应回客户端。- psm:
api/file/download:在Send处选择Send and Download。 源码: /springmvc4/ - src:
c.y.controller.FileController
@RequestMapping("download")
public ResponseEntity<byte[]> download(String file) throws IOException {
byte[] bytes = FileUtils.readFileToByteArray(new File("F:\\upload\\" + file));
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment", file);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<>(bytes, headers, HttpStatus.CREATED);
}
5. 拦截器类
概念: 拦截器用于拦截请求和响应,多个拦截器可组成拦截器链:
- 开发拦截器类:实现
HandlerInterceptor接口,并重写其中的三个方法:preHandle():拦截请求,返回true表示放行,返回false会终止程序,请求结束。postHandle():拦截响应,该方法可以对ModelAndView进行操作。afterCompletion():渲染视图后执行。
- 在主配中使用
<mvc:interceptors >/<mvc:interceptor >配置拦截器类,默认拦截所有请求:<mvc:mapping path="">用于指定拦截的请求。<mvc:exclude-mapping path="">用于指定不拦截的请求。- 二者同时存在时,取交集。
源码: /springmvc4/
- res:
spring/springmvc.xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/api/interceptor/test"/>
<bean class="com.yap.interceptor.InterceptorA"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/api/interceptor/test"/>
<bean class="com.yap.interceptor.InterceptorB"/>
</mvc:interceptor>
</mvc:interceptors>
- src:
c.y.interceptor.InterceptorA
package com.yap.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author yap
*/
public class InterceptorA implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
System.out.println("InterceptorA:preHandle()...");
return true;
}
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse resp,Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("InterceptorA:postHandle()...");
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) throws Exception {
System.out.println("InterceptorA:afterCompletion()...");
}
}
- src:
c.y.interceptor.InterceptorB
package com.yap.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author yap
*/
public class InterceptorB implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
System.out.println("InterceptorB:preHandle()...");
return true;
}
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse resp,Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("InterceptorB:postHandle()...");
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) throws Exception {
System.out.println("InterceptorB:afterCompletion()...");
}
}
- src:
c.y.controller.InterceptorController
package com.yap.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author yap
*/
@Controller
@RequestMapping("/api/interceptor")
public class InterceptorController {
@ResponseBody
@RequestMapping("test")
public String test() {
System.out.println("test()...");
return "success";
}
}
6. 异常处理
概念: HandlerExceptionResolver 是spring异常处理的顶级接口,其每个实现类都是种异常处理方式:
ExceptionHandlerExceptionResolver:使用注解方式捕获和处理异常:- 开发异常处理方法,有且仅有一个异常类型参数,不能设置其他参数。
- 对方法标记
@ExceptionHandler并使用value指定捕获的异常类,缺省表示捕获全部异常。 - 该方法可以移动到
@ControllerAdvice标记的类中以上升作用范围,该类需要被spring扫描。
ResponseStatusExcpetionResovler:使用注解方式自定义异常页面:- 开发异常页面方法,标记
@ResponseStatus并使用value/reason设置http状态码/异常原因。 - 该方法可以移动到
@ResponseStatus标记的异常子类中以上升作用范围,该类需要被spring扫描。
- 开发异常页面方法,标记
SimpleMappingExceptionResolver:通过XML配置来处理异常:- 主配文件中管理
o.s.w.s.h.SimpleMappingExceptionResolver类。 - 注入
exceptionAttribute以指定存储异常信息的变量名,默认exception,存于请求域。 - 注入
exceptionMappings以指定捕获哪些异常及对应跳转页面,页面值使用视图解析器的前后缀。
- 主配文件中管理
源码: /springmvc4/
- src:
c.y.util.ExceptionUtil
package com.yap.util;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author yap
*/
@Component
@ControllerAdvice
public class ExceptionUtil {
@ResponseBody
@ExceptionHandler({ArithmeticException.class, ArrayIndexOutOfBoundsException.class})
public String exceptionHandler(Exception e) {
System.out.println("ExceptionUtil.exceptionHandler()..." + e);
return "error";
}
}
- src:
c.y.util.ExceptionUtil
package com.yap.util;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* @author yap
*/
@Component
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "MyNotFoundException: 页面走丢了!")
public class MyNotFoundException extends Exception {
}
- src:
c.y.controller.ExceptionController
package com.yap.controller;
import com.yap.util.MyNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* @author yap
*/
@RequestMapping("/api/exception")
@Controller
public class ExceptionController {
@ResponseBody
@RequestMapping("exception-handler-test")
public String exceptionHandlerTest(Integer num) {
System.out.println("test()...");
if (num == 0) {
throw new ArithmeticException();
}
if (num == 1) {
throw new ArrayIndexOutOfBoundsException();
}
return "success";
}
@ResponseBody
@ExceptionHandler(ArithmeticException.class)
public String exceptionHandler(Exception e) {
System.out.println("ExceptionController.exceptionHandler()..." + e);
return "error";
}
@RequestMapping("response-status-test")
public String responseStatusTest(Integer num) {
System.out.println("responseStatusTest()...");
return num == 0 ? "forward:response-status" : "success";
}
@RequestMapping("response-status")
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "ExceptionController: 页面走丢了!")
public void responseStatus() {
System.out.println("responseStatus()...");
}
@ResponseBody
@RequestMapping("my-not-found-exception-test")
public String myNotFoundExceptionTest(Integer num) throws MyNotFoundException {
System.out.println("myNotFoundExceptionTest()...");
if (num == 0) {
throw new MyNotFoundException();
}
return "success";
}
<!--xml方式配置-->
@ResponseBody
@RequestMapping("xml-exception-test")
public String xmlExceptionTest(Integer num) {
System.out.println("xmlExceptionTest()...");
if (num == 0) {
throw new NullPointerException();
}
return "success";
}
}
- res:
spring/springmvc.xml
<!--XML配置-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionAttribute" value="ex" />
<property name="exceptionMappings">
<props>
<prop key="java.lang.NullPointerException">forward:/view/error.jsp</prop>
</props>
</property>
</bean>
- web:
view/error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<section>
<p>异常信息: <%=request.getAttribute("ex")%>
</section>
</body>
</html>
7. 国际化属性
概念: ResourceBundleMessageSource 类用于在响应阶段加载和读取国际化属性文件:
- 在classpath下创建属性文件
基名_语言_地区.properties。 - 在主配文件中管理
o.s.c.s.ResourceBundleMessageSource,id固定为messageSource:- 注入
basenames以设置属性文件基名。 - 注入
defaultEncoding以设置编码。
- 注入
- 在JSP中使用spring标签读取国际化文件:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %><spring:message code="">读取国际化属性文件中的key值。
- cli:
api/i18n/test:F12- 搜索语言- 将英语(美国)移到顶部。 源码: /springmvc4/ - res:
spring/springmvc.xml
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames" value="i18n"/>
<property name="defaultEncoding" value="utf-8"/>
</bean>
- res:
i18n_zh_CN.properties
title=请登录您的账号
username=账号
password=密码
submit=登录
- res:
i18n_en_US.properties
title=plz login your account!
username=username
password=password
submit=login
- src:
c.y.controller.I18nController
package com.yap.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author yap
*/
@Controller
@RequestMapping("/api/i18n")
public class I18nController {
@RequestMapping("test")
public String test() {
System.out.println("test()...");
return "forward:/view/i18n.jsp";
}
}
- web:
view/i18n.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--重点--%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1><spring:message code="title"/></h1>
<form action="">
<label><spring:message code="username"/></label>
<label><input type="text"/></label><br/>
<label><spring:message code="password"/></label>
<label><input type="password"/></label><br/>
<button><spring:message code="submit"/></button>
</form>
</body>
</html>