Skip to content

Commit efdb8fb

Browse files
committed
JSR校验
1 parent 4355237 commit efdb8fb

File tree

6 files changed

+145
-3
lines changed

6 files changed

+145
-3
lines changed

note/images/Validator.png

18.5 KB
Loading

note/spring-mvc.md

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1266,4 +1266,130 @@ WebDataBinderFactory接口用以创建WebDataBinder对象,其继承体系如
12661266
protected ServletRequestDataBinder createBinderInstance(Object target, String objectName, NativeWebRequest request) {
12671267
return new ExtendedServletRequestDataBinder(target, objectName);
12681268
}
1269-
```
1269+
```
1270+
1271+
参数绑定的入口位于ModelAttributeMethodProcessor.resolveArgument方法,相关源码:
1272+
1273+
```java
1274+
if (!mavContainer.isBindingDisabled(name)) {
1275+
bindRequestParameters(binder, webRequest);
1276+
}
1277+
```
1278+
1279+
接下来由ServletRequestDataBinder的bind方法完成,核心源码:
1280+
1281+
```java
1282+
public void bind(ServletRequest request) {
1283+
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
1284+
doBind(mpvs);
1285+
}
1286+
```
1287+
1288+
在ServletRequestParameterPropertyValues构造器中获取了Request中所有的属性对。doBind方法便是调用前面初始化的目标对象的setter方法进行参数设置的过程,不再展开。
1289+
1290+
### 参数校验
1291+
1292+
将我们的Controller方法改写为下面这种形式便可以启动Spring MVC的参数校验:
1293+
1294+
```java
1295+
@RequestMapping("/echoAgain")
1296+
public String echo(@Validated SimpleModel simpleModel, Model model) {
1297+
model.addAttribute("echo", "hello " + simpleModel.getName() + ", your age is " + simpleModel.getAge() + ".");
1298+
System.out.println(model.asMap().get("simpleModel"));
1299+
return "echo";
1300+
}
1301+
```
1302+
1303+
在这里@Validated注解可以用@Valid(javax)替换,前者是Spring对java校验标准的扩充,增加了校验组的支持。
1304+
为什么参数校验要放到参数绑定后面进行说明呢,因为**@Validated@valid注解不会影响Spring MVC参数解析的行为,被这两个注解标注的对象仍是由参数绑定一节提到的解析器进行解析。**
1305+
1306+
当参数校验绑定之后,Spring MVC会尝试对参数进行校验,如果我们设置了校验注解。ModelAttributeMethodProcessor.resolveArgument方法相关源码:
1307+
1308+
```java
1309+
validateIfApplicable(binder, parameter);
1310+
1311+
protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
1312+
Annotation[] annotations = methodParam.getParameterAnnotations();
1313+
for (Annotation ann : annotations) {
1314+
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
1315+
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
1316+
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
1317+
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
1318+
binder.validate(validationHints);
1319+
break;
1320+
}
1321+
}
1322+
}
1323+
```
1324+
1325+
DataBinder.validate:
1326+
1327+
```java
1328+
public void validate(Object... validationHints) {
1329+
for (Validator validator : getValidators()) {
1330+
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
1331+
((SmartValidator) validator).validate(getTarget(), getBindingResult(), validationHints);
1332+
} else if (validator != null) {
1333+
validator.validate(getTarget(), getBindingResult());
1334+
}
1335+
}
1336+
}
1337+
```
1338+
1339+
可见,具体的校验交给了`org.springframework.validation.Validator`实现,类图:
1340+
1341+
![Validator](images/Validator.png)
1342+
1343+
getValidators方法获取的实际上是DataBinder内部的validators字段:
1344+
1345+
```java
1346+
private final List<Validator> validators = new ArrayList<Validator>();
1347+
```
1348+
1349+
根据这里的校验器的来源可以分为以下两种情况。
1350+
1351+
#### JSR校验
1352+
1353+
需要引入hibernate-validator到classpath中,回顾最前面配置解析部分,配置:
1354+
1355+
```xml
1356+
<mvc:annotation-driven/>
1357+
```
1358+
1359+
会利用AnnotationDrivenBeanDefinitionParser进行相关的解析、初始化工作,正是在其parse方法完成了对JSR校验的支持。相关源码:
1360+
1361+
```java
1362+
@Override
1363+
public BeanDefinition parse(Element element, ParserContext parserContext) {
1364+
RuntimeBeanReference validator = getValidator(element, source, parserContext);
1365+
}
1366+
1367+
private RuntimeBeanReference getValidator(Element element, Object source, ParserContext parserContext) {
1368+
//mvc:annotation-driven配置支持validator属性
1369+
if (element.hasAttribute("validator")) {
1370+
return new RuntimeBeanReference(element.getAttribute("validator"));
1371+
} else if (javaxValidationPresent) {
1372+
RootBeanDefinition validatorDef = new RootBeanDefinition(
1373+
"org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean");
1374+
validatorDef.setSource(source);
1375+
validatorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
1376+
String validatorName = parserContext.getReaderContext().registerWithGeneratedName(validatorDef);
1377+
parserContext.registerComponent(new BeanComponentDefinition(validatorDef, validatorName));
1378+
return new RuntimeBeanReference(validatorName);
1379+
} else {
1380+
return null;
1381+
}
1382+
}
1383+
```
1384+
1385+
javaxValidationPresent的定义:
1386+
1387+
```java
1388+
private static final boolean javaxValidationPresent =
1389+
ClassUtils.isPresent("javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
1390+
```
1391+
1392+
OptionalValidatorFactoryBean实现了InitializingBean接口,所以afterPropertiesSet方法是其初始化的入口,具体的校验过程不再展开。
1393+
1394+
#### 自定义校验器
1395+

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@
9494
<version>3.2.5</version>
9595
</dependency>
9696

97+
<dependency>
98+
<groupId>org.hibernate.validator</groupId>
99+
<artifactId>hibernate-validator</artifactId>
100+
<version>6.0.2.Final</version>
101+
</dependency>
102+
97103
</dependencies>
98104

99105
<build>

src/main/java/controller/SimpleController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import model.SimpleModel;
44
import org.springframework.stereotype.Controller;
55
import org.springframework.ui.Model;
6-
import org.springframework.web.bind.annotation.ModelAttribute;
6+
import org.springframework.validation.annotation.Validated;
77
import org.springframework.web.bind.annotation.RequestMapping;
88

99
/**
@@ -21,7 +21,7 @@ public String echo(String name, Model model) {
2121
}
2222

2323
@RequestMapping("/echoAgain")
24-
public String echo(@ModelAttribute SimpleModel simpleModel, Model model) {
24+
public String echo(@Validated SimpleModel simpleModel, Model model) {
2525
model.addAttribute("echo", "hello " + simpleModel.getName() + ", your age is " + simpleModel.getAge() + ".");
2626
System.out.println(model.asMap().get("simpleModel"));
2727
return "echo";

src/main/java/model/SimpleModel.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package model;
22

3+
import javax.validation.constraints.NotBlank;
4+
35
/**
46
* 简单的model.
57
*
68
* @author skywalker
79
*/
810
public class SimpleModel {
911

12+
@NotBlank
1013
private String name;
1114
private Integer age;
1215

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package validator;
2+
3+
/**
4+
* @author skywalker
5+
*/
6+
public class SimpleModelValidator {
7+
}

0 commit comments

Comments
 (0)