文章主要内容:
- 数据格式转化与格式化
- Hibernate Validator数据校验
- 拦截器
- Json数据处理
二、SpringMVC常用技术
本节示例项目的准备与第一节大致一致:
- 导包:
- 添加全局配置文件:
其中依赖包括:bean context mvc
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<context:component-scan base-package="com.cyt.springmvc"></context:component-scan>
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<mvc:default-servlet-handler/>
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
- 配置web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
<display-name>springmvc2</display-name>
<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
其中注意
<param-value>classpath:springmvc.xml</param-value>
和<url-pattern>/</url-pattern>
1 数据格式转化与格式化
1.1 Converter
-
Spring MVC框架的Converter<S, T>是一个可以将一种数据类型转换成另一种数据类型的接口,这里S表示源类型,T表示目标类型。
-
开发者在实际应用中,使用框架内置的类型转换器基本就够了,但有时需要编写具有特定功能的类型转换器。
示例:将页面表单上输入的字符串转化为对象,例如将格式为字符串”name;price;amount”转换为一个Product对象
- 设计javabean
Product.java
public class Product {
private String name;
private Double price;
private Integer amount;
- controller:
ProductController.java
@Controller
public class ProductController {
@PostMapping("/addProduct")
public String addProduct(@RequestParam("product")Product product) {
System.out.println(product);
return "success";
}
}
- product.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<h1>Add Product</h1>
<form action="addProduct" method="post">
Product: <input type="text" name="product"/><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
- 创建一个转换器 converter
public class StringProductConverter implements Converter<String, Product> {
@Override
public Product convert(String source) {
if (source!=null) {
String[] values = source.split(";");
if (values!=null && values.length==3) {
String name = values[0];
Double price = Double.parseDouble(values[1]);
Integer amount = Integer.parseInt(values[2]);
Product product = new Product(name, price, amount);
return product;
}
}
return null;
}
}
- springmvc.xml
单单这样是不能进行自动转化的,要在全局配置文件中配置一个框架内的bean:
<bean id="formattingConversion" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.cyt.springmvc.converter.StringProductConverter"></bean>
</list>
</property>
</bean>
- success.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Success</title>
</head>
<body>
<h1>Success</h1>
</body>
</html>
1.2 Formmater
-
Spring MVC框架的Formatter与Converter<S, T>一样,也是一个可以将一种数据类型转换成另一种数据类型的接口。但不同的是,Formatter的源数据类型必须是String类型,而Converter<S, T>的源数据类型是任意数据类型。
-
在Web应用中,由HTTP发送的请求数据到控制器中都是以String类型获取。因此,在Web应用中选择Formatter比选择Converter<S, T>更加合理。
示例:日期数据格式化,例如将格式为”yyyy-MM-dd”日期格式化为Date类型
这里的string pattern规则是: " yyyy-MM-dd HH:mm:ss " 注意区分大写小,MM指月份,mm指分钟,HH是24小
时制,而hh是12小时制。
- product.jsp
<h1>add Date</h1>
<form action="addDate" method="post">
Date: <input type="text" name="date"/><br><br>
<input type="submit" value="Submit">
</form>
- controller:
@PostMapping("/addDate")
public String addDate(Date date) {
System.out.println(date);
return "success";
}
- success.jsp 与上小节无区别
- 创建一个Formatter:MyDateFormatter.java
public class MyDateFormatter implements Formatter<Date> {
@Override
public String print(Date date, Locale locale) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
if (date!=null) {
return format.format(date);
}
else
return null;
}
@Override
public Date parse(String time, Locale locale) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
if (time!=null) {
return format.parse(time);
}
else
return null;
}
}
- 配置全局配置文件
springmvc.xml
<bean id="formattingConversion" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!-- converters 为上一节留下来的 -->
<property name="converters 为上一节留下来的 -->">
<list>
<bean class="com.cyt.springmvc.converter.StringProductConverter"></bean>
</list>
</property>
<property name="formatters">
<list>
<bean class="com.cyt.springmvc.converter.MyDateFormatter"></bean>
</list>
</property>
</bean>
2 Hibernate Validator数据校验
数据验证分为客户端验证和服务器端验证,客户端验证主要是过滤正常用户的误操作,主要通过JavaScript代码完成;服务器端验证是整个应用阻止非法数据的最后防线,主要通过在应用中编程实现。
2.1 空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
2.2 Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
2.3 长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
2.4 日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。
2.5 邮件地址
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
2.6 数值检查
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 被指定的元素必须在合适的范围内
2.7 示例
- 先导入Hibernate Validator所需jar包:
common-logging为之前导入,也是必需
- 新建一个bean,并在属性上注解:
Staff.java:
public class Staff {
@NotEmpty(message = "{staff.name.required}")
private String name;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past(message = "{staff.birth.past}")
private Date birth;
@NumberFormat(pattern = "$#,###,###.##")
private Double salary;
@NumberFormat(pattern = "#,###,###")
private Integer sells;
@Email(message = "{staff.email.format}")
@NotEmpty(message = "{staff.email.required}")
private String email;
@NotEmpty(message = "{staff.password.required}")
@Length(min = 6,max = 12,message = "{staff.password.length}")
private String password;
- 定义错误消息资源文件
errorMessages.properties:
staff.name.required=name can't be empty
staff.birth.past=birth can't be a past time
staff.email.format=email is illegal
staff.email.required=email can't be empty
staff.password.required=password can't be empty
staff.password.length=password's length should between 6 and 12
typeMismatch.staff.birth=the block need a date
- 使用@Valid注解进行数据验证
controller:
@Controller
public class StaffController {
@GetMapping("/inputStaff")
public String inputStaff(Model model) {
model.addAttribute("staff",new Staff());
return "inputStaff";
}
@PostMapping("addStaff")
public String addStaff(@Valid Staff staff, BindingResult result) throws UnsupportedEncodingException {
if (result.hasErrors()) {
return "inputStaff";
}else {
System.out.println(staff);
return "success";
}
}
}
在形参 Staff staff 前加上@Valid:
@Valid Staff staff
- 在jsp页面上显示错误消息
<body>
<h1>input Staff Info</h1>
<form:form modelAttribute="staff" action="addStaff" method="post">
Name: <form:input path="name"/><form:errors path="name"></form:errors><br><br>
Birth: <form:input path="birth"/><form:errors path="birth"></form:errors><br><br>
Salary: <form:input path="salary"/><form:errors path="salary"></form:errors><br><br>
Sells: <form:input path="sells"/><form:errors path="sells"></form:errors><br><br>
Email: <form:input path="email"/><form:errors path="email"></form:errors><br><br>
Password: <form:password path="password"/><form:errors path="password"></form:errors><br><br>
<input type="submit" value="Submit">
</form:form>
</body>
使用<form:errors path=""> 进行显示错误消息
以上
modelAttribute="staff"
是 taglib中的form标签规定需要先准备模型数据,故,上面controller第6行代码
model.addAttribute("staff",new Staff());
为taglib中的form标签提供模型数据
- 配置全局配置文件
springmvc.xml:
<!-- 配置hibernate-validator所需资源文件,在这里是错误消息文件 -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>/WEB-INF/prop/errorMessages</value>
</list>
</property>
<property name="fileEncodings" value="utf-8"></property>
<property name="cacheSeconds" value="60"></property>
</bean>
<!-- 注册校验器 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"></property>
<property name="validationMessageSource" ref="messageSource"></property>
</bean>
<mvc:default-servlet-handler/>
<mvc:annotation-driven conversion-service="formattingConversion" validator="validator">
</mvc:annotation-driven>
注意:记得在mvc:annotation-driven中加入 validator="validator"
3 拦截器
3.1 创建 HandlerInterceptor 并测试
- FirstInterceptor.java:
package com.cyt.springmvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("FirstInterceptor ======> preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor ======> postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("FirstInterceptor ======> afterCompletion");
}
}
实现 HandlerInterceptor 接口 并实现三个接口方法
-
preHandle()方法:该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作。返回true表示继续向下执行,返回false表示中断后续操作。
-
postHandle()方法:该方法在控制器的处理请求方法调用之后,解析视图之前执行。可以通过此方法对请求域中的模型和视图做进一步的修改。
-
afterCompletion()方法:该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行。可以通过此方法实现一些资源清理等工作。
- 全局配置文件
springmvc.xml 配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**/*"/>
<bean class="com.cyt.springmvc.interceptor.FirstInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
- controller:
InterceptorController.java
@Controller
public class InterceptorController {
@GetMapping("/interceptor")
public String interceptor() {
System.out.println("do business logic......");
return "success";
}
}
- jsp:
<a href="interceptor">Go to interceptorController</a>
控制台输出:
FirstInterceptor ======> preHandle
do business logic......
FirstInterceptor ======> postHandle
FirstInterceptor ======> afterCompletion
- 可以在postHandle()方法中对视图做修改:
例如:
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
modelAndView.setViewName("inputStaff");
modelAndView.addObject("staff",new Staff());
System.out.println("FirstInterceptor ======> postHandle");
}
其中
modelAndView.addObject("staff",new Staff());
是因为inputStaff试图中设置了ModelAttribute提前准备数据。
modelAndView.setViewName("inputStaff");
设置之后,拦截器会把请求转发到inputStaff。
3.2 拦截其他文件
由于我们在全局配置文件中配置了:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**/*"/>
<bean class="com.cyt.springmvc.interceptor.FirstInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
<mvc:mapping path="/**/*"/>
可以拦截所有资源,包括静态资源等
- 静态资源拦截:
在Webcontent下创建相应目录和文件,然后访问:
http://localhost:8080/springmvc2/txt/a.txt
拦截器一样可以起效
- 拦截器排除
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**/*"/>
<mvc:exclude-mapping path="/txt/a.txt"/>
<bean class="com.cyt.springmvc.interceptor.FirstInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
<mvc:exclude-mapping path="/txt/a.txt"/>
不拦截/txt/a.txt
或<mvc:exclude-mapping path="/txt/*.txt"/>
不拦截txt下所有txt文件
3.2 多个拦截器
配置两个拦截器,SecondInterceptor 在FirstInterceptor 之后配置
SecondInterceptor .java
package com.cyt.springmvc.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class SecondInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("SecondInterceptor ======> preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("SecondInterceptor ======> postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("SecondInterceptor ======> afterCompletion");
}
}
全局配置文件
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**/*"/>
<mvc:exclude-mapping path="/txt/a.txt"/>
<bean class="com.cyt.springmvc.interceptor.FirstInterceptor"></bean>
</mvc:interceptor>
<bean class="com.cyt.springmvc.interceptor.SecondInterceptor"></bean>
</mvc:interceptors>
效果:(控制台打印)
FirstInterceptor ======> preHandle
SecondInterceptor ======> preHandle
do business logic......
SecondInterceptor ======> postHandle
FirstInterceptor ======> postHandle
SecondInterceptor ======> afterCompletion
FirstInterceptor ======> afterCompletion
- 注意拦截器注册的先后顺序,以及拦截器三个方法起效的先后顺序
- 注意!!!!在配置文件注册拦截器时,可以直接用<bean>标签,就默认全部拦截,不设任何mapping,例如这个例子中的
<bean class="com.cyt.springmvc.interceptor.SecondInterceptor"></bean>
4 Json数据处理
导入jar包:
4.1 向页面发送json数据
index.jsp
<a href="getJson">Get Json Data</a>
javabean:Product.java
public class Product {
private String name;
private Double price;
private Integer amount;
private Date produceTime;
public Product(String name, Double price, Integer amount) {
super();
this.name = name;
this.price = price;
this.amount = amount;
this.produceTime = new Date();
}
controller:
JsonController.java
@Controller
public class JsonController {
@ResponseBody
@GetMapping("/getJson")
public Product getJson() {
Product product = new Product("shoes", 100.5, 3);
return product;
}
}
用Chrome打开网址http://localhost:8080/springmvc2/index.jsp点击链接可以看到:
{"name":"shoes","price":100.5,"amount":3,"produceTime":1604572798092}
4.2 接收页面发送的数据
- controller:
@ResponseBody
@GetMapping("/json")
public Product json(@RequestBody Product product) {
System.out.println(product);
Product result = new Product("clothes", 50.6, 3);
return result;
}
这里返回值没有实际用处
- 用Postman 软件给链接: http://localhost:8080/springmvc2/json 发送数据
控制台把接收到的数据封装成类并打印
FirstInterceptor ======> preHandle
SecondInterceptor ======> preHandle
Product [name=shoes, price=100.5, amount=3, produceTime=Wed Nov 04 18:45:37 CST 2020]
SecondInterceptor ======> postHandle
FirstInterceptor ======> postHandle
SecondInterceptor ======> afterCompletion
FirstInterceptor ======> afterCompletion
4.3 在接收数据时做数据校验
这里用Product的name属性做示例
- 在封装类中做注解:
public class Product {
@NotEmpty(message = "{product.name.required}")
private String name;
private Double price;
private Integer amount;
private Date produceTime;
- 在errorMessages.properties中添加:
product.name.required=the product name can't be empty
- controller:
@ResponseBody
@GetMapping("/json")
public Product json(@RequestBody @Valid Product product,BindingResult result) {
if (result.hasErrors()) {
System.out.println(result.getFieldError("name").getDefaultMessage());
} else {
System.out.println(product);
}
// 下面代码只是为了返回值而设定
Product prod = new Product("clothes", 50.6, 3);
return prod;
}
- 用Postman发送json数据:
{"name":"","price":100.5,"amount":3,"produceTime":1604486737127}
控制台输出:
FirstInterceptor ======> preHandle
SecondInterceptor ======> preHandle
the product name can't be empty
SecondInterceptor ======> postHandle
FirstInterceptor ======> postHandle
SecondInterceptor ======> afterCompletion
FirstInterceptor ======> afterCompletion
====== SpringMVC 끝나다 !======