数据格式化
提交数据(比如表单),对提交的数据进行转换和处理
基本数据类型可以和字符串自动转换
<a href="<%=request.getContextPath()%>/addMonsterUI">添加妖怪</a>
@Controller
@Scope(value = "prototype")
public class MonsterHandler {
@RequestMapping(value = "/addMonsterUI")
public String addMonsterUI(Map<String, Object> map) {
map.put("monster", new Monster());
return "datavalid/monster_addUI";
}
@RequestMapping(value = "/save")
public String save(Monster monster) {
System.out.println("----monster---" + monster);
return "datavalid/success";
}
}
return "datavalid/monster_addUI";配置文件写了前缀后缀
<form:form action="save" method="post" modelAttribute="monster">
妖怪名字: <form:input path="name"/> <form:errors path="name"/> <br><br>
妖怪年龄~: <form:input path="age"/> <form:errors path="age"/> <br><br>
电子邮件: <form:input path="email"/> <form:errors path="email"/> <br><br>
<input type="submit" value="添加妖怪"/>
</form:form>
1. 使用springMVC的标签来完成
2. SpringMVC 表单标签在显示之前必须在 request 中有一个 bean, 该 bean 的属性和表单标签的字段要对应!
3. SpringMVC 的 form:form 标签的 action 属性值中的 / 不代表 WEB 应用的根目录.
4. <form:form action="?" method="POST" modelAttribute="monster">
5. 当我们向map添加的数据时,会默认存放到request域
特殊数据类型和字符串间的转换
特殊数据类型和字符串之间的转换使用注解(比如日期,规定格式的小数比如货币形式等)
日期@DateTimeFormat 货币@NumberFormat
public class Monster() {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@NumberFormat(pattern = "###,###.##")
private Float salary;
}
@NumberFormat 只要能转成数字,1234.11也可以
@Controller
@Scope(value = "prototype")
public class MonsterHandler {
@RequestMapping(value = "/addMonsterUI")
public String addMonsterUI(Map<String, Object> map) {
map.put("monster", new Monster());
return "datavalid/monster_addUI";
}
@RequestMapping(value = "/save")
public String save(Monster monster) {
System.out.println("----monster---" + monster);
return "datavalid/success";
}
}
<form:form action="save" method="post" modelAttribute="monster">
妖怪生日: <form:input path="birthday"/> <form:errors path="birthday"/> <br>
妖怪薪水: <form:input path="salary"/> <form:errors path="salary"/> <br>
<input type="submit" value="添加妖怪"/>
</form:form>
验证及国际化
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在JavaEE中
JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则
HibernateValidator 扩展注解
public class Monster {
private Integer id;
@NotEmpty
private String email;
@NotNull(message = "age不能为空")
@Range(min = 1,max = 100)
private Integer age;
@NotEmpty
private String name;
@NotNull(message = "生日不能为空")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@NotNull(message = "薪水不能为空")
@NumberFormat(pattern = "###,###.##")
private Float salary;
}
1、 @Valid 对 monster 数据按照注解进行验证
2、Errors errors 表示如果校验出现错误,将校验的错误信息保存 errors
3、Map<String, Object> map 表示如果校验出现错误, 将校验的错误信息保存 map 同时保存monster对象
4、校验发生的时机: 在springmvc底层,反射调用目标方法时,会接收到http请求的数据,然后根据注解来进行验证
@RequestMapping(value = "/save")
public String save(@Valid Monster monster, Errors errors, Map<String, Object> map) {
System.out.println("----monster---" + monster);
//我们为了看到验证的情况,我们输出map 和 errors
System.out.println("===== map ======");
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " value=" + entry.getValue());
}
System.out.println("===== errors ======");
if (errors.hasErrors()) {//判断是否有错误
List<ObjectError> allErrors = errors.getAllErrors();
for (ObjectError error : allErrors) {
System.out.println("error=" + error);
}
return "datavalid/monster_addUI";
}
return "datavalid/success";
}
map会输出对象信息,并且会显示有几个错误,输出错误信息(跟Errors功能一样)
Errors输出错误信息
如果没有错误,map也会输出对象信息
回显错误信息
<form:form action="save" method="post" modelAttribute="monster">
妖怪名字: <form:input path="name"/> <form:errors path="name"/> <br><br>
妖怪年龄~: <form:input path="age"/> <form:errors path="age"/> <br><br>
电子邮件: <form:input path="email"/> <form:errors path="email"/> <br><br>
妖怪生日: <form:input path="birthday"/> <form:errors path="birthday"/> <br>
妖怪薪水: <form:input path="salary"/> <form:errors path="salary"/> <br>
<input type="submit" value="添加妖怪"/>
</form:form>
自定义验证错误信息
在springDispatcherServlet-servlet.xml配置
<!-- 配置国际化错误信息的资源处理bean -->
<bean id="messageSource" class=
"org.springframework.context.support.ResourceBundleMessageSource">
<!-- 配置国际化文件名字
表示messageSource回到 src/i18nXXX.properties去读取错误信息
-->
<property name="basename" value="i18n"></property>
</bean>
i18n.properties
NotEmpty.monster.name=\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
typeMismatch.monster.age=\u5e74\u9f84\u8981\u6c42\u5728\u0031\u002d\u0031\u0035\u0030\u4e4b\u95f4
typeMismatch.monster.birthday=\u751f\u65e5\u683c\u5f0f\u4e0d\u6b63\u786e
typeMismatch.monster.salary=\u85aa\u6c34\u683c\u5f0f\u4e0d\u6b63\u786e
只加@Range,如果不在范围,会显示 需要在 x - x 之间,可以指定message
如果在国际化文件写了Range.monster.age,优先自定义信息
@NotNull会显示不能为null
@NotEmpty会显示不能为空
Range.monster.age=\u8fd9\u662f\u65b0\u7684\u9a8c\u8bc1\u9519\u8bef\u4fe1\u606f-\u5e74\u9f84\u57281-100\u4e4b\u95f4
细节
Bean字段加验证注解
目标方法,在Bean类型的参数前加@Valid注解,之后添加Errors或BindingResult类型参数
表单回显错误信息
配置文件,中文需要Unicode编码,使用工具转码
格式:验证规则.表单modelAttribute值.属性名=消息信息@NotEmpty,String、colletcion、map、array不为null,不为empty
@NotNull,可以接收任何类型,不为null
数据类型转换校验核心类DataBinder
工作机制
SpringMVC 通过反射机制对目标方法进行解析,将请求消息绑定到处理方法的入参中。
数据绑定的核心部件是 DataBinder
在数据校验前也会发生错误(比如数据类型转换/格式化),会放在BindingResult中
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
@Nullable
private ConversionService conversionService;//用于数据转换
private final List<Validator> validators;//校验器封装到集合
@Nullable
private AbstractPropertyBindingResult bindingResult;//存放校验错误的结果
public void validate() {
Object target = this.getTarget();
Assert.state(target != null, "No target to validate");
BindingResult bindingResult = this.getBindingResult();
Iterator var3 = this.getValidators().iterator();
while(var3.hasNext()) {
Validator validator = (Validator)var3.next();
validator.validate(target, bindingResult);
}
}
}
因为加了@Valid注解,需要对Bean实例进行校验
target就是表单提交的Bean实例数据
bindingResult的运行类型是BeanPropertyBindingResult,就是errors
如果在Bean字段加了@DateTimeFormat、@NumberFormat会进行数据类型转换/格式化,出错直接存放在bindingResult中,跳过数据校验这个步骤
拿到校验器进行遍历,如果Bean实例没通过校验,就会将错误放在bindingResult中
取消某个属性的绑定
使用@InitBinder注解标识方法,对WebDataBinder对象进行初始化
WebDataBinder对象是DataBinder的子类,用于完成由表单字段到Bean属性的绑定
@InitBinder方法的参数通常是WebDataBinder,返回必须是void
机制:springmvc 在底层通过反射调用目标方法时, 接收到http请求的参数和值,使用反射+注解技术取消对指定属性的填充
setDisallowedFields支持可变参数,可以填写多个字段
如果我们取消某个属性绑定,验证无意义,把验证的注解去掉, name属性会使用默认值null
@InitBinder
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.setDisallowedFields("name","age");
}
中文乱码处理
表单提交数据为中文时,会出现乱码
自定义过滤器
<filter>
<filter-name>MyCharacterFilter</filter-name>
<filter-class>com.hspedu.web.filter.MyCharacterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyCharacterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
public class MyCharacterFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
//放行请求
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {}
}
Spring提供过滤器
放在所有过滤器之前,只需要配置xml,不再写过滤器
设置为true,保证按照指定格式转换
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
处理json和HttpMessageConverter<T>
处理Json-@ResponseBody
目标方法 @ResponseBody,返回的数据是json格式
控制台返回Json数据
@Data
public class Dog {
private String name;
private String address;
}
@Controller
public class JsonHandler {
@RequestMapping(value = "/json/dog")
@ResponseBody
public Dog getJson() {
//返回对象
//springmvc会根据你的设置,转成json格式数据返回
Dog dog = new Dog();
dog.setName("大黄狗");
dog.setAddress("小新的家");
return dog;
}
}
<html>
<head>
<title>json提交</title>
<script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
$(function () {
//给id="getJson"绑定点击事件
$("#getJson").click(function () {
var url = this.href;
var args = {"time": new Date};//为了防止页面缓存
$.post(
url,
args,
function (data) {//data 就是返回的数据,是json格式=>如果是多个json数据,可以遍历
console.log("dataa= ", data);
console.log("dog.name=", data.name)
console.log("dog.addresss=", data.address)
},
"json"
);
return false;//这里我们返回false,就不使用href默认机制
})
})
</script>
</head>
<body>
<h1>请求一个json数据</h1>
<a href="<%=request.getContextPath()%>/json/dog" id="getJson">点击获取json数据</a>
</body>
</html>
处理Json-@ResquestBody
以前通过 表单(POST) 或者 URL请求携带参数名=参数值(GET) 把数据提交给目标方法
使用SpringMVC的 @RequestBody 将客户端提交的json数据,封装成JavaBean对象,再把这个javabean以json对象形式返回
@Data
public class User {
private String userName;
private Integer age;
}
@RequestBody User user 在形参指定了 @RequestBody
springmvc就会将提交的json字符串数据填充给指定Javabean
@Controller
public class JsonHandler {
@RequestMapping(value = "/save2")
@ResponseBody
public User save2(@RequestBody User user) {
//将前台传过来的数据 以json的格式相应回浏览器
System.out.println("user~= " + user);
return user;
}
}
<html>
<head>
<script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
$(function () {
$("button[name='butt1']").click(function () {
//目标:将userName 和 age 封装成json字符串,发送给目标方法
var url = "/springmvc/save2";
var userName = $("#userName").val();
var age = $("#age").val();
//将json对象转成json字符串
var args = JSON.stringify({"userName": userName, "age": age});
$.ajax({
url: url,
data: args,
type: "POST",
success: function (data) {
console.log("返回的data= ", data);
},
//下面这个contentType参数,是指定发送数据的编码和格式
contentType: "application/json;charset=utf-8"
})
})
})
</script>
</head>
<body>
<h1>发出一个json数据</h1>
u:<input id="userName" type="text"><br/>
a:<input id="age" type="text"><br/>
<button name="butt1">添加用户</button>
</body>
</html>
细节
@ResponseBody可以返回一个对象,也可以是集合
@RequestMapping(value = "/json/dogs")
@ResponseBody
public List<Dog> getJsons() {
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog("大黄狗", "小新的家"));
dogs.add(new Dog("大黄狗2", "小新2的家"));
dogs.add(new Dog("大黄狗3", "小新3的家"));
return dogs;
}
@ResponseBody可以直接写在controller上,对所有的方法生效
@ResponseBody + @Controller 可以直接写成 @RestController
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
HttpMessageConverter<T>
SpringMVC 处理 JSON-底层实现是依靠HttpMessageConverter来进行转换的
根据消息头来查找对应的实现子类,再去找到对应的转换器,在转换器中封装,传给SpringMVC,再根据@ResponseBody、HttpEntity<T>等信息找到对应转换器
1、到达转换器进行转换,有两个参数
inputMessage封装了请求头和请求数据
javaType,Bean的全路径
2、指定字符集(application/json;charset=utf-8)
3、ObjectMapper处理Json数据
把请求数据封装到javaType指定的对象中,inputMessage封装了目标方法
返回给SpringMVC的目标方法
最后return user还要经过转换器进行回送
回送的数据格式根据@ResponseBody等自动选择
文件下载-ResponseEntity<T>
<body>
<h1>下载文件的测试 </h1>
<a href="<%=request.getContextPath()%>/downFile">点击下载文件</a>
</body>
@RequestMapping(value = "/downFile")
public ResponseEntity<byte[]> downFile(HttpSession session)
throws Exception {
//1. 先获取到下载文件的inputStream
InputStream resourceAsStream =
session.getServletContext().getResourceAsStream("/img/2.jpg");
//2. 开辟一个存放文件的byte数组, byte[] 是可以支持二进制数据(图片,视频。)
byte[] bytes = new byte[resourceAsStream.available()];
//3. 将下载文件的数据,读入到byte[]
resourceAsStream.read(bytes);
//public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {}
//4. 创建返回的HttpStatus
HttpStatus httpStatus = HttpStatus.OK;
//5. 创建 headers
HttpHeaders headers = new HttpHeaders();
//指定返回的数据,客户端应当以附件形式处理
headers.add("Content-Disposition", "attachment;filename=2.jpg");
//构建一个ResponseEntity 对象1. 的http响应头headers 2. http响应状态 3. 下载的文件数据
ResponseEntity<byte[]> responseEntity =
new ResponseEntity<>(bytes, headers, httpStatus);
//如果出现找不到文件,解决方法 rebuild project -> 重启tomcat
return responseEntity;
}
文件上传
<h1>文件上传的演示</h1>
<form action="<%=request.getContextPath()%>/fileUpload" method="post" enctype="multipart/form-data">
<%--
在handler的形参加上字段就可以获取到introduce
中文乱码使用过滤器来处理
--%>
文件介绍:<input type="text" name="introduce"><br>
选择文件:<input type="file" name="file"><br>
<input type="submit" value="上传文件">
</form>
<!--配置文件上传需要的bean-->
<!--这里的id不能乱写,底层通过父接口id来查找-->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
id="multipartResolver"/>
@Controller
public class FileUploadHandler {
//编写方法,处理文件上传的请求
@RequestMapping(value = "/fileUpload")
public String fileUpload(@RequestParam(value = "file") MultipartFile file,
HttpServletRequest request, String introduce) throws IOException {
//接收到提交的文件名
String originalFilename = file.getOriginalFilename();
System.out.println("你上传的文件名= " + originalFilename);
System.out.println("introduce=" + introduce);
//得到要把上传文件保存到哪个路径[全路径:包括文件名]
String fileFullPath =
request.getServletContext().getRealPath("/img/" + originalFilename);
//创建文件
File saveToFile = new File(fileFullPath);
//将上传的文件,转存到saveToFile
file.transferTo(saveToFile);
return "success";
}
}
自定义拦截器
如果用户提交的数据有禁用词,在第一个拦截器就返回,不执行目标方法
@Component
public class MyInterceptor01 implements HandlerInterceptor {
/**
* 1. preHandle() 在目标方法执行前被执行
* 2. 如果preHandle() 返回false , 不再执行目标方法
* 3. 该方法可以获取到request, response, handler
* 4. 这里根据业务,可以进行拦截,并指定跳转到哪个页面
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("--MyInterceptor01--preHandle()---");
//获取到用户提交的关键字
String keyword = request.getParameter("keyword");
if("病毒".equals(keyword)) {
//请求转发到warning
request.getRequestDispatcher("/WEB-INF/pages/warning.jsp")
.forward(request,response);
return false;
}
System.out.println("得到到keyword= "+ keyword);
return true;
}
/**
* 1. 在目标方法执行后,会执行postHandle
* 2. 该方法可以获取到 目标方法,返回的ModelAndView对象
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("--MyInterceptor01--postHandle()--");
}
/**
* afterCompletion() 在视图渲染后被执行, 这里可以进行资源清理工作
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("--MyInterceptor01--afterCompletion()--");
}
}
配置拦截器
1. 创建实现 HandlerInterceptor 接口的 bean
2. 在 mvc:interceptors 中配置拦截器
第一种:自己在interceptors配置一个引用指向你需要使用的拦截器,对所有的请求都拦截
<mvc:interceptors>
<ref bean="myInterceptor01"/>
</mvc:interceptors>
第二种:默认配置所有方法都拦截,也可以指定拦截目标方法
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hi"/>
<ref bean="myInterceptor01"/>
</mvc:interceptor>
</mvc:interceptors>
第三种:支持通配符,同时指定不对哪些目标方法进行拦截
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/h*"/>
<mvc:exclude-mapping path="/hello"/>
<ref bean="myInterceptor01"/>
</mvc:interceptor>
</mvc:interceptors>
多个拦截器执行流程
按配置顺序执行
如果A的pre方法返回false,直接返回
如果B的pre方法返回false,执行A的after方法
异常处理
局部异常
1. localException 方法处理局部异常
2. 这里处理ArithmeticException.class,NullPointerException.class
3. Exception ex: 生成的异常对象,会传递给ex, 通过ex可以得到相关的信息
@Controller
public class MyExceptionHandler {
@ExceptionHandler({ArithmeticException.class,NullPointerException.class})
public String localException(Exception ex, HttpServletRequest request){
System.out.println("局部异常信息是-" + ex.getMessage());
//将异常的信息带到下一个页面.
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
@RequestMapping(value = "/testException01")
public String test01(Integer num) {
int i = 9 / num;
return "success";
}
}
<body>
<h1>朋友, 程序发生了异常...</h1>
异常信息- ${requestScope.reason}
</body>
执行流程
ExceptionHandlerMethodResolver先找到对应异常.class,然后拿到方法名
全局异常
1. 全局异常就不管是哪个Handler抛出的异常,都可以捕获
@ExceptionHandler({异常类型})
2. 这里的全局异常是NumberFormatException.class,ClassCastException.class
3. Exception ex 接收抛出的异常对象
@ControllerAdvice
public class MyGlobalException {
@ExceptionHandler({NumberFormatException.class, ClassCastException.class, AgeException.class})
public String globalException(Exception ex, HttpServletRequest request) {
System.out.println("全局异常处理-" + ex.getMessage());
//将异常的信息带到下一个页面.
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
}
@Controller
public class MyExceptionHandler {
@ExceptionHandler({ArithmeticException.class,NullPointerException.class,NumberFormatException.class})
public String localException(Exception ex, HttpServletRequest request){
System.out.println("局部异常信息是-" + ex.getMessage());
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
@RequestMapping(value = "/testGlobalException")
public String global(){
//1. 模拟了一个异常 NumberFormatException
//2. 该异常没有在局部异常处理,按照异常处理机制,就会交给全局异常处理类处理
int num = Integer.parseInt("hello");
return "success";
}
}
局部异常 > 全局异常 > SimpleMappingExceptionResolver > tomcat默认机制
自定义异常
@ResponseStatus
@ResponseStatus(reason = "年龄需要在1-120之间", value = HttpStatus.BAD_REQUEST)
public class AgeException extends RuntimeException {
public AgeException() {
}
public AgeException(String message) {
super(message);
}
}
@Controller
public class MyExceptionHandler {
@RequestMapping(value = "/testException02")
public String test02(){
throw new AgeException("年龄必须在1-120之间~~~");
}
}
@ResponseStatus的内容在tomcat默认页面展示出来,并没有传给getMessage()
使用构造器就可以把内容传给getMessage()
可以被全局异常接管,@ExceptionHandler添加自定义异常类名.class即可
@ControllerAdvice
public class MyGlobalException {
@ExceptionHandler({ClassCastException.class, AgeException.class})
public String globalException(Exception ex, HttpServletRequest request) {
System.out.println("全局异常处理-" + ex.getMessage());
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
}
统一处理异常信息SimpleMappingExceptionResolver
没有设置局部异常和全局异常
key是异常的全类名,根据视图解析器,指定对应的页面名
<!--配置统一处理异常Bean-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>
</props>
</property>
</bean>
@Controller
public class MyExceptionHandler {
@RequestMapping(value = "/testException03")
public String test03(){
int[] arr = new int[]{3,9,10,190};
//抛出一个数组越界的异常 ArrayIndexOutOfBoundsException
System.out.println(arr[90]);
return "success";
}
}
对未知异常进行统一处理
对发生了没有归类的异常,可以给出统一提示页面
<prop key="java.lang.Exception">allEx</prop>