diff --git a/README.md b/README.md index 64687b4fbe3c3485aaa8ca2fa841008ca5d9bc42..113996f89d7f280155023d6634ee9b6eb14d7424 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ Github仓库地址为 https://gitee.com/archguide/ArchGuide ##### [(十五)Spring中的格式化](docs/Spring/Spring官网学习笔记/Spring官网阅读(十五)Spring中的格式化/Spring官网阅读(十五)Spring中的格式化.md) +##### [(十六)Spring中的数据绑定](docs/Spring/Spring官网学习笔记/Spring官网阅读(十六)Spring中的数据绑定/Spring官网阅读(十六)Spring中的数据绑定.md) + +##### [(十七)Spring中的数据绑定](docs/Spring/Spring官网学习笔记/Spring官网阅读(十七)Spring中的数据校验/Spring官网阅读(十七)Spring中的数据校验.md) + ### Spring杂谈 ##### [从java的Type到Spring的ResolvableType](docs/Spring/Spring杂谈/从java的Type到Spring的ResolvableType/从java的Type到Spring的ResolvableType.md) diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214.md" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214.md" new file mode 100644 index 0000000000000000000000000000000000000000..4d151a848a38850c7230722eef6badc640905aae --- /dev/null +++ "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214.md" @@ -0,0 +1,904 @@ +Spring官网阅读(十七)Spring中的数据校验 + +> 在前文中我们一起学习了Spring中的数据绑定,也就是整个`DataBinder`的体系,其中有提到`DataBinder`跟校验相关。可能对于Spring中的校验大部分同学跟我一一样,都只是知道可以通过`@Valid` / `@Validated`来对接口的入参进行校验,但是对于其底层的具体实现以及一些细节都不是很清楚,通过这篇文章我们就来彻底搞懂Spring中的校验机制。 +> +> 在学习Spring中某个功能时,往往要从Java本身出发。比如我们之前介绍过的Spring中的国际化(见《Spring官网阅读(十一)》)、Spring中的`ResolvableType`(见《Spring杂谈》系列文章)等等,它们都是对Java本身的封装,沿着这个思路,我们要学习Spring中的数据校验,必然要先对Java中的数据校验有一定了解。 +> +> 话不多说,开始正文! + +# Java中的数据校验 + +> 在学习Java中的数据校验前,我们需要先了解一个概念,即什么是`JSR`? +> +> `JSR`:全称Java Specification Requests,意思是Java 规范提案。我们可以将其理解为Java为一些功能指定的一系列统一的规范。跟数据校验相关的最新的`JSR`为`JSR 380`。 +> +> Bean Validation 2.0 是JSR第380号标准。该标准连接如下:https://www.jcp.org/en/egc/view?id=380 +> Bean Validation的主页:http://beanvalidation.org +> Bean Validation的参考实现:https://github.com/hibernate/hibernate-validator + +## Bean Validation(JSR 380) + +![](./image/2020040201.png) + +从官网中的截图我们可以看到,`Bean Validation 2.0`的唯一实现就是[Hibernate Validator](http://hibernate.org/validator/),对应版本为`6.0.1.Final`,同时在2.0版本之前还有1.1(JSR 349)及1.0(JSR 303)两个版本,不过版本间的差异并不是我们关注的重点,而且`Bean Validation 2.0`本身也向下做了兼容。 + +> 在上面的图中,可以看到`Bean Validation2.0`的全称为`Jakarta Bean Validation2.0`,关于Jakarta,感兴趣的可以参考这个链接:https://www.oschina.net/news/94055/jakarta-ee-new-logo,就是Java换了个名字。 + +## 使用示例 + +导入依赖: + +```xml + + + org.apache.tomcat.embed + tomcat-embed-el + 9.0.16 + compile + + + org.hibernate.validator + hibernate-validator + 6.0.14.Final + compile + + +``` + +测试Demo: + +```java +@Data +public class Person { + + @NotEmpty + private String name; + + @Positive + @Max(value = 100) + private int age; +} + +public class SpringValidation { + public static void main(String[] args) { + Person person = new Person(); + person.setAge(-1); + Set> result = + Validation.buildDefaultValidatorFactory().getValidator().validate(person); + // 对结果进行遍历输出 + result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()) + .forEach(System.out::println); + } + // 运行结果: + // name 不能为空: null + // age 必须是正数: -1 +} +``` + +对于其中涉及的细节目前来说我不打算过多的探讨,我们现在只需要知道Java提供了数据校验的规范,同时Hibernate对其有一套实现就可以了,并且我们也验证了使用其进行校验是可行的。那么接下来我们的问题就变成了Spring对Java的这套数据校验的规范做了什么支持呢?或者它又做了什么扩展呢? + +# Spring对Bean Validation的支持 + +我们先从官网入手,看看Spring中如何使用数据校验,我这里就直接取官网中的Demo了 + +```java +@Data +public class Person { + private String name; + private int age; +} + +public class PersonValidator implements Validator { + @Override + public boolean supports(Class clazz) { + return Person.class.equals(clazz); + } + + @Override + public void validate(Object obj, Errors e) { + ValidationUtils.rejectIfEmpty(e, "name", "name.empty"); + Person p = (Person) obj; + if (p.getAge() < 0) { + e.rejectValue("age", "negativevalue"); + } else if (p.getAge() > 110) { + e.rejectValue("age", "too.darn.old"); + } + } +} + +public class Main { + public static void main(String[] args) { + Person person = new Person(); + person.setAge(-1); + DirectFieldBindingResult errorResult = new DirectFieldBindingResult(person, "dmz"); + PersonValidator personValidator = new PersonValidator(); + personValidator.validate(person, errorResult); + System.out.println(errorResult); + // 程序打印: +//Field error in object 'dmz' on field 'name': rejected value [null]; codes //[name.empty.dmz.name,name.empty.name,name.empty.java.lang.String,name.empty]; arguments //[]; default message [null] +//Field error in object 'dmz' on field 'age': rejected value [-1]; codes //[negativevalue.dmz.age,negativevalue.age,negativevalue.int,negativevalue]; arguments //[]; default message [null] + + } +} +``` + +在上面的例子中,`PersonValidator`实现了一个`Validator`接口,这个接口是Spring自己提供的,全称:`org.springframework.validation.Validator`,我们看看这个接口的定义 + +## Spring中的Validator + +> `org.springframework.validation.Validator`是专门用于应用相关的对象的校验器。 +> +> 这个接口完全从基础设施或者上下文中脱离的,这意味着它没有跟web层或者数据访问层或者其余任何的某一个层次发生耦合。所以它能用于应用中的任意一个层次,能对应用中的任意一个对象进行校验。, + +### 接口定义 + +```java +public interface Validator { + + // 此clazz是否可以被validate + boolean supports(Class clazz); + + // 执行校验,错误消息放在Errors中 + // 如果能执行校验,通常也意味着supports方法返回true + // 可以参考ValidationUtils这个工具类 + void validate(Object target, Errors errors); +} +``` + +### UML类图 + +![](./image/2020040402.png) + +#### SmartValidator + +> 对Validator接口进行了增强,能进行分组校验 + +```java +public interface SmartValidator extends Validator { + + // validationHints:就是启动的校验组 + // target:需要校验的结果 + // errors:封装校验 + void validate(Object target, Errors errors, Object... validationHints); + + // 假设value将被绑定到指定对象中的指定字段上,并进行校验 + // @since 5.1 这个方法子类需要复写 否则不能使用 + default void validateValue(Class targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) { + throw new IllegalArgumentException("Cannot validate individual value for " + targetType); + } +} +``` + +#### SpringValidatorAdapter + +> 在之前的接口我们会发现,到目前为止Spring中的校验跟Bean Validation还没有产生任何交集,而SpringValidatorAdapter就完成了到Bean Validation的对接 + +```java +// 可以看到,这个接口同时实现了Spring中的SmartValidator接口跟JSR中的Validator接口 +public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator { + + //@NotEmpty,@NotNull等注解都会有这三个属性 + private static final Set internalAnnotationAttributes = new HashSet<>(4); + static { + internalAnnotationAttributes.add("message"); + internalAnnotationAttributes.add("groups"); + internalAnnotationAttributes.add("payload"); + } + + // targetValidator就是实际完成校验的对象 + @Nullable + private javax.validation.Validator targetValidator; + public SpringValidatorAdapter(javax.validation.Validator targetValidator) { + Assert.notNull(targetValidator, "Target Validator must not be null"); + this.targetValidator = targetValidator; + } + SpringValidatorAdapter() { + } + void setTargetValidator(javax.validation.Validator targetValidator) { + this.targetValidator = targetValidator; + } + + // 支持对所有类型的Bean的校验 + @Override + public boolean supports(Class clazz) { + return (this.targetValidator != null); + } + + // 调用targetValidator完成校验,并通过processConstraintViolations方法封装校验后的结果到Errors中 + @Override + public void validate(Object target, Errors errors) { + if (this.targetValidator != null) { + processConstraintViolations(this.targetValidator.validate(target), errors); + } + } + + // 完成分组校验 + @Override + public void validate(Object target, Errors errors, Object... validationHints) { + if (this.targetValidator != null) { + processConstraintViolations( + this.targetValidator.validate(target, asValidationGroups(validationHints)), errors); + } + } + + // 完成对对象上某一个字段及给定值的校验 + @SuppressWarnings("unchecked") + @Override + public void validateValue( + Class targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) { + + if (this.targetValidator != null) { + processConstraintViolations(this.targetValidator.validateValue( + (Class) targetType, fieldName, value, asValidationGroups(validationHints)), errors); + } + } + + + // @since 5.1 + // 将validationHints转换成JSR中的分组 + private Class[] asValidationGroups(Object... validationHints) { + Set> groups = new LinkedHashSet<>(4); + for (Object hint : validationHints) { + if (hint instanceof Class) { + groups.add((Class) hint); + } + } + return ClassUtils.toClassArray(groups); + } + + // 省略对校验错误的封装 + // ..... + + + + // 省略对JSR中validator接口的实现,都是委托给targetValidator完成的 + // ...... + +} + +``` + +#### ValidatorAdapter + +> 跟SpringValidatorAdapter同一级别的类,但是不同的是他没有实现JSR中的Validator接口。一般不会使用这个类 + +#### CustomValidatorBean + +```java +public class CustomValidatorBean extends SpringValidatorAdapter implements Validator, InitializingBean { + + // JSR中的接口,校验器工厂 + @Nullable + private ValidatorFactory validatorFactory; + + // JSR中的接口,用于封装校验信息 + @Nullable + private MessageInterpolator messageInterpolator; + + // JSR中的接口,用于判断属性能否被ValidatorProvider访问 + @Nullable + private TraversableResolver traversableResolver; + + // 忽略setter方法 + + // 在SpringValidatorAdapter的基础上实现了InitializingBean,在Bean初始化时调用,用于给上面三个属性进行配置 + @Override + public void afterPropertiesSet() { + if (this.validatorFactory == null) { + this.validatorFactory = Validation.buildDefaultValidatorFactory(); + } + + ValidatorContext validatorContext = this.validatorFactory.usingContext(); + MessageInterpolator targetInterpolator = this.messageInterpolator; + if (targetInterpolator == null) { + targetInterpolator = this.validatorFactory.getMessageInterpolator(); + } + validatorContext.messageInterpolator(new LocaleContextMessageInterpolator(targetInterpolator)); + if (this.traversableResolver != null) { + validatorContext.traversableResolver(this.traversableResolver); + } + + setTargetValidator(validatorContext.getValidator()); + } + +} +``` + +#### LocalValidatorFactoryBean + +```java +public class LocalValidatorFactoryBean extends SpringValidatorAdapter + implements ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean { + //...... +} +``` + +可以看到,这个类额外实现了`ValidatorFactory`接口,所以通过它不仅能完成校验,还能获取一个校验器validator。 + +#### OptionalValidatorFactoryBean + +```java +public class OptionalValidatorFactoryBean extends LocalValidatorFactoryBean { + + @Override + public void afterPropertiesSet() { + try { + super.afterPropertiesSet(); + } + catch (ValidationException ex) { + LogFactory.getLog(getClass()).debug("Failed to set up a Bean Validation provider", ex); + } + } + +} +``` + +继承了LocalValidatorFactoryBean,区别在于让校验器的初始化成为可选的,即使校验器没有初始化成功也不会报错。 + +## 使用示例 + +在对整个体系有一定了解之后,我们通过一个例子来体会下Spring中数据校验 + +```java +public class Main { + public static void main(String[] args) { + AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); + // 将CustomValidatorBean注册到容器中,主要是为了让它经过初始化阶段完成对校验器的配置 + ac.register(CustomValidatorBean.class); + // 刷新启动容器 + ac.refresh(); + // 获取到容器中的校验器 + CustomValidatorBean cb = ac.getBean(CustomValidatorBean.class); + + // 校验simple组的校验 + Person person = new Person(); + DirectFieldBindingResult simpleDbr = new DirectFieldBindingResult(person, "person"); + cb.validate(person, simpleDbr, Person.Simple.class); + + // 校验Complex组的校验 + DirectFieldBindingResult complexDbr = new DirectFieldBindingResult(person, "person"); + person.setStart(new Date()); + cb.validate(person, complexDbr, Person.Complex.class); + System.out.println(complexDbr); + } +} +``` + +运行结果我这里就不贴出来了,大家可以自行测试 + +------ + +到目前为止,我们所接触到的校验的内容跟实际使用还是有很大区别,我相信在绝大多数情况下大家都不会采用前文所采用的这种方式去完成校验,而是通过`@Validated`或者`@Valid`来完成校验。 + +# @Validated跟@Valid的区别 + +关于二者的区别网上有很多文章,但是实际二者的区别大家不用去记,我们只要看一看两个注解的申明变一目了然了。 + +## @Validated + +```java +// Target代表这个注解能使用在类/接口/枚举上,方法上以及方法的参数上 +// 注意注意!!!! 它不能注解到字段上 +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER}) +// 在运行时期仍然生效(注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在) +@Retention(RetentionPolicy.RUNTIME) +// 这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中,是一个标记注解,没有成员。 +@Documented +public @interface Validated { + // 校验时启动的分组 + Class[] value() default {}; + +} +``` + +## @Valid + +```java +// 可以作用于类,方法,字段,构造函数,参数,以及泛型类型上(例如:Main<@Valid T> ) +// 简单来说,哪里都可以放 +@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE }) +@Retention(RUNTIME) +@Documented +public @interface Valid { + //没有提供任何属性 +} +``` + +我们通过上面两个注解的定义就能很快的得出它们的区别: + +1. **来源不同**,`@Valid`是`JSR`的规范,来源于`javax.validation`包下,而`@Validated`是Spring自身定义的注解,位于`org.springframework.validation.annotation`包下 + +2. **作用范围不同**,`@Validated`无法作用在字段上,正因为如此它就无法完成对级联属性的校验。而`@Valid`的 + + 没有这个限制。 + +3. **注解中的属性不同**,`@Validated`注解中可以提供一个属性去指定校验时采用的分组,而`@Valid`没有这个功能,因为`@Valid`不能进行分组校验 + +> 我相信通过这个方法的记忆远比看博客死记要好~ + +# 实际生产应用 + +> 我们将分为两部分讨论 +> +> 1. 对Java的校验 +> 2. 对普通参数的校验 +> +> 这里说的普通参数的校验是指参数没有被封装到JavaBean中,而是直接使用,例如: +> +> test(String name,int age),这里的name跟age就是简单的参数。 +> +> 而将name跟age封装到JavaBean中,则意味着这是对JavaBean的校验。 +> +> 同时,按照校验的层次,我们可以将其分为 +> +> 1. 对controller层次(接口层)的校验 +> 2. 对普通方法的校验 +> +> 接下来,我们就按这种思路一一进行分析 +> +> 子所以按照层次划分是因为Spring在对接口上的参数进行校验时,跟对普通的方法上的参数进行校验采用的是不同的形式(*虽然都是依赖于JSR的实现来完成的,但是调用JSR的手段不一样*) + +### 对JavaBean的校验 + +**待校验的类** + +```java +@Data +public class Person { + + // 错误消息message是可以自定义的 + @NotNull//(groups = Simple.class) + public String name; + + @Positive//(groups = Default.class) + public Integer age; + + @NotNull//(groups = Complex.class) + @NotEmpty//(groups = Complex.class) + private List<@Email String> emails; + + // 定义两个组 Simple组和Complex组 + public interface Simple { + } + + public interface Complex { + + } +} + +// 用于进行嵌套校验 +@Data +public class NestPerson { + @NotNull + String name; + + @Valid + Person person; +} +``` + +#### 对controller(接口)层次上方法参数的校验 + +**用于测试的接口** + +```java +// 用于测试的接口 +@RestController +@RequestMapping("/test") +public class Main { + + // 测试 @Valid对JavaBean的校验效果 + @RequestMapping("/valid") + public String testValid( + @Valid @RequestBody Person person) { + System.out.println(person); + return "OK"; + } + + // 测试 @Validated对JavaBean的校验效果 + @RequestMapping("/validated") + public String testValidated( + @Validated @RequestBody Person person) { + System.out.println(person); + return "OK"; + } + + // 测试 @Valid对JavaBean嵌套属性的校验效果 + @RequestMapping("/validNest") + public String testValid(@Valid @RequestBody NestPerson person) { + System.out.println(person); + return "OK"; + } + + // 测试 @Validated对JavaBean嵌套属性的校验效果 + @RequestMapping("/validatedNest") + public String testValidated(@Validated @RequestBody NestPerson person) { + System.out.println(person); + return "OK"; + } +} +``` + +**测试用例** + +```java +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SpringFxApplication.class) +public class MainTest { + + @Autowired + private WebApplicationContext context; + + @Autowired + ObjectMapper objectMapper; + + MockMvc mockMvc; + + Person person; + + NestPerson nestPerson; + + @Before + public void init() { + person = new Person(); + person.setAge(-1); + person.setName(""); + person.setEmails(new ArrayList<>()); + nestPerson = new NestPerson(); + nestPerson.setPerson(person); + mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + } + + @Test + public void testValid() throws Exception { + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/test/valid") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(person)); + MvcResult mvcResult = mockMvc.perform(builder).andReturn(); + Exception resolvedException = mvcResult.getResolvedException(); + System.out.println(resolvedException.getMessage()); + assert mvcResult.getResponse().getStatus()==200; + } + + @Test + public void testValidated() throws Exception { + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/test/validated") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(person)); + MvcResult mvcResult = mockMvc.perform(builder).andReturn(); + Exception resolvedException = mvcResult.getResolvedException(); + System.out.println(resolvedException.getMessage()); + assert mvcResult.getResponse().getStatus()==200; + } + + @Test + public void testValidNest() throws Exception { + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/test/validatedNest") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(nestPerson)); + MvcResult mvcResult = mockMvc.perform(builder).andReturn(); + Exception resolvedException = mvcResult.getResolvedException(); + System.out.println(resolvedException.getMessage()); + assert mvcResult.getResponse().getStatus()==200; + } + + @Test + public void testValidatedNest() throws Exception { + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/test/validatedNest") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(nestPerson)); + MvcResult mvcResult = mockMvc.perform(builder).andReturn(); + Exception resolvedException = mvcResult.getResolvedException(); + System.out.println(resolvedException.getMessage()); + assert mvcResult.getResponse().getStatus()==200; + } + +} +``` + +**测试结果** + +![](./image/2020040504.png) + +我们执行用例时会发现,四个用例均断言失败并且控制台打印:Validation failed for argument ...........。 + +另外细心的同学可以发现,Spring默认有一个全局异常处理器`DefaultHandlerExceptionResolver` + +同时观察日志我们可以发现,全局异常处理器处理的异常类型为:`org.springframework.web.bind.MethodArgumentNotValidException` + +##### 使用注意要点 + +> 1. **如果想使用分组校验的功能必须使用@Validated** +> 2. 不考虑分组校验的情况,`@Validated`跟`@Valid`没有任何区别 +> +> 3. 网上很多文章说`@Validated`不支持对嵌套的属性进行校验,这种说法是不准确的,大家可以对第三,四个接口方法做测试,运行的结果是一样的。更准确的说法是`@Validated`不能作用于字段上,而`@Valid`可以。 + +#### 对普通方法的校验 + +待测试的方法 + +```java +@Service +//@Validated +//@Valid +public class DmzService { + public void testValid(@Valid Person person) { + System.out.println(person); + } + + public void testValidated(@Validated Person person) { + System.out.println(person); + } +} +``` + +测试用例 + +```java +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SpringFxApplication.class) +public class DmzServiceTest { + + @Autowired + DmzService dmzService; + + Person person; + + @Before + public void init(){ + person = new Person(); + person.setAge(-1); + person.setName(""); + person.setEmails(new ArrayList<>()); + } + + @Test + public void testValid() { + dmzService.testValid(person); + } + + @Test + public void testValidated() { + dmzService.testValidated(person); + } +} +``` + +我们分为三种情况测试 + +1. **类上不添加任何注解** + +![](./image/2020040503.png) + +2. **类上添加@Validated注解** + +![](./image/2020040501.png) + +3. **类上添加@Valid注解** + +![](./image/2020040502.png) + +##### 使用注意要点 + +> 通过上面的例子,我们可以发现,只有类上添加了`@Vlidated`注解,并且待校验的JavaBean上添加了`@Valid`的情况下校验才会生效。 +> +> 所以当我们要对普通方法上的JavaBean参数进行校验必须满足下面两个条件 +> +> 1. 方法所在的类上添加`@Vlidated` +> 2. 待校验的JavaBean参数上添加`@Valid` + +------ + +### 对简单参数校验 + +#### 对普通方法的校验 + +**用于测试的方法** + +```java +@Service +@Validated +//@Valid +public class IndexService { + public void testValid(@Max(10) int age,@NotBlank String name) { + System.out.println(age+" "+name); + } + + public void testValidated(@Max(10) int age,@NotBlank String name) { + System.out.println(age+" "+name); + } + + public void testValidNest(@Max(10) int age,@NotBlank String name) { + System.out.println(age+" "+name); + } + + public void testValidatedNest(@Max(10) int age,@NotBlank String name) { + System.out.println(age+" "+name); + } +} +``` + +**测试用例** + +```java +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SpringFxApplication.class) +public class IndexServiceTest { + @Autowired + IndexService indexService; + + int age; + + String name; + + @Before + public void init(){ + age=100; + name = ""; + } + @Test + public void testValid() { + indexService.testValid(age,name); + } + @Test + public void testValidated() { + indexService.testValidated(age,name); + } + @Test + public void testValidNest() { + indexService.testValidNest(age,name); + } + @Test + public void testValidatedNest() { + indexService.testValidatedNest(age,name); + } +} +``` + +这里的测试结果我就不再放出来了,大家猜也能猜到答案 + +##### 使用注意要点 + +> 1. 方法所在的类上添加`@Vlidated`(`@Valid`注解无效),跟JavaBean的校验是一样的 + +#### 对controller(接口)层次的校验 + +```java +@RestController +@RequestMapping("/test/simple") +// @Validated +public class ValidationController { + + @RequestMapping("/valid") + public String testValid( + @Valid @Max(10) int age, @Valid @NotBlank String name) { + System.out.println(age + " " + name); + return "OK"; + } + + @RequestMapping("/validated") + public String testValidated( + @Validated @Max(10) int age, @Valid @NotBlank String name) { + System.out.println(age + " " + name); + return "OK"; + } +} +``` + +在测试过程中会发现,不过是在参数前添加了`@Valid`或者`@Validated`校验均不生效。这个时候不得不借助Spring提供的普通方法的校验功能来完成数据校验,也就是在类级别上添加` @Valiv=dated`(参数前面的`@Valid`或者`@Validated`可以去除) + +##### 使用注意要点 + +> 对于接口层次简单参数的校验需要借助Spring对于普通方法校验的功能,必须在类级别上添加` @Valiv=dated`注解。 + +### 注意 + +在上面的所有例子中我都是用SpringBoot进行测试的,如果在单纯的SpringMVC情况下,如果对于普通方法的校验不生效请添加如下配置: + +```java +@Bean +public MethodValidationPostProcessor methodValidationPostProcessor() { + return new MethodValidationPostProcessor(); +} +``` + +实际上对于普通方法的校验,就是通过这个后置处理器来完成的,它会生成一个代理对象帮助我们完成校验。SpringBoot中默认加载了这个后置处理器,而SpringMVC需要手动配置 + +## 结合BindingResult使用 + +在上面的例子中我们可以看到,当对于接口层次的JavaBean进行校验时,如果校验失败将会抛出`org.springframework.web.bind.MethodArgumentNotValidException`异常,这个异常将由Spring默认的全局异常处理器进行处理,但是有时候我们可能想在接口中拿到具体的错误进行处理,这个时候就需要用到`BindingResult`了 + +如下: + +![](./image/2020040505.png) + +可以发现,错误信息已经被封装到了`BindingResult`,通过`BindingResult`我们能对错误信息进行自己的处理。请注意,这种做法只对接口中JavaBean的校验生效,对于普通参数的校验是无效的。 + +------ + +> 实际上经过上面的学习我们会发现,其实Spring中的校验就是两种(前面的分类是按场景分的) +> +> 1. Spring在接口上对JavaBean的校验 +> 2. Spring在普通方法上的校验 +> +> 第一种校验失败将抛出`org.springframework.web.bind.MethodArgumentNotValidException`异常,而第二种校验失败将抛出`javax.validation.ConstraintViolationException`异常 +> +> 为什么会这样呢? +> +> 这是因为,对于接口上JavaBean的校验是Spring在对参数进行绑定时做了一层封装,大家可以看看`org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument`这段代码 +> +> ```java +> public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, +> NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { +> +> parameter = parameter.nestedIfOptional(); +> Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); +> String name = Conventions.getVariableNameForParameter(parameter); +> +> if (binderFactory != null) { +> // 获取一个DataBinder +> WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); +> if (arg != null) { +> // 进行校验,实际上就是调用DataBinder完成校验 +> validateIfApplicable(binder, parameter); +> // 如果校验出错并且没有提供BindingResult直接抛出一个MethodArgumentNotValidException +> if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { +> throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); +> } +> } +> if (mavContainer != null) { +> mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); +> } +> } +> +> return adaptArgumentIfNecessary(arg, parameter); +> } +> ``` +> +> 但是对于普通方法的校验时,Spring完全依赖于动态代理来完成参数的校验。具体细节在本文中不多赘述,大家可以关注我后续文章,有兴趣的同学可以看看这个后置处理器:`MethodValidationPostProcessor` + +## 结合全局异常处理器使用 + +在实际应用中,更多情况下我们结合全局异常处理器来使用数据校验的功能,实现起来也非常简单,如下: + +```java +@RestControllerAdvice +public class MethodArgumentNotValidExceptionHandler { + // 另外还有一个javax.validation.ConstraintViolationException异常处理方式也类似,这里不再赘述 + // 关于全局异常处理器的部分因为是跟SpringMVC相关的,另外牵涉到动态代理,所以目前我也不想做过多介绍 + // 大家只要知道能这么用即可,实际的使用可自行百度,非常简单 + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleMethodArgumentNotValid(MethodArgumentNotValidException ex) { + BindingResult bindingResult = ex.getBindingResult(); + + StringBuilder stringBuilder = new StringBuilder(); + for (FieldError error : bindingResult.getFieldErrors()) { + String field = error.getField(); + Object value = error.getRejectedValue(); + String msg = error.getDefaultMessage(); + String message = String.format("错误字段:%s,错误值:%s,原因:%s;", field, value, msg); + stringBuilder.append(message).append("\r\n"); + } + return Result.error(MsgDefinition.ILLEGAL_ARGUMENTS.codeOf(), stringBuilder.toString()); + } +} +``` + +# 总结 + +关于数据校验我们就介绍到这里了,其实我自己之前对Spring中具体的数据校验的使用方法及其原理都非常的模糊,但是经过这一篇文章的学习,现在可以说知道自己用了什么了并且知道怎么用,也知道为什么。这也是我写这篇文章的目的。按照惯例,我们还是总结了一张图,如下: + +![](./image/2020040506.png) + + + diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040201.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040201.png" new file mode 100644 index 0000000000000000000000000000000000000000..6b006005d2e060a0c873b79bb40646b57e503e42 Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040201.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040401.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040401.png" new file mode 100644 index 0000000000000000000000000000000000000000..d2f1bbb008f1084f215eeb3b6a5bdf3f35f6712f Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040401.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040402.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040402.png" new file mode 100644 index 0000000000000000000000000000000000000000..1b98ea7808f123458305325f080143ed5a455d9d Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040402.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040501.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040501.png" new file mode 100644 index 0000000000000000000000000000000000000000..adac779a36cf012dd8e1ae8e0630f389c6e9a800 Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040501.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040502.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040502.png" new file mode 100644 index 0000000000000000000000000000000000000000..ad1b4519c5f3e6789280bf8bc065c9ce61fe0270 Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040502.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040503.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040503.png" new file mode 100644 index 0000000000000000000000000000000000000000..de8a9006f9546940dbf3713ba25b3e7a7ba4689b Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040503.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040504.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040504.png" new file mode 100644 index 0000000000000000000000000000000000000000..21b3bbcfcbb8d0e5a8c741d11e1122f7a1c286ee Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040504.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040505.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040505.png" new file mode 100644 index 0000000000000000000000000000000000000000..dfe1e598e6627bd4ea09ea7ea1d5e20107aa8f2b Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040505.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040506.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040506.png" new file mode 100644 index 0000000000000000000000000000000000000000..7662086246b35bd45795b57b81d0b81cef475c92 Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\344\270\203\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\346\240\241\351\252\214/image/2020040506.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232.md" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232.md" new file mode 100644 index 0000000000000000000000000000000000000000..1cb97b75ebc3f78ff66e291855fcbd0ca408d78e --- /dev/null +++ "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232.md" @@ -0,0 +1,646 @@ +Spring官网阅读(十六)Spring中的数据绑定 + +**鲁班学院:戴明智(D55)** + +> 在前面的文章我们学习过了Spring中的类型转换以及格式化,对于这两个功能一个很重要的应用场景就是应用于我们在XML中配置的Bean的属性值上,如下: +> +> ```xml +> +> +> +> +> +> ``` +> +> 在上面这种情况下,我们从XML中解析出来的值类型肯定是String类型,而对象中的属性为int类型,当Spring将配置中的数据应用到Bean上时,就调用了我们的类型转换器完成了String类型的字面值到int类型的转换。 +> +> 那么除了在上面这种情况中使用了类型转换,还有哪些地方用到了呢?对了,就是本文要介绍的数据绑定--`DataBinder`。 + +# DataBinder + +## UML类图 + +![](./image/2020033101.png) + +从上图我们可以看到,`DataBinder`实现了`PropertyEditorRegistry`以及`TypeConverter`,所以它拥有类型转换的能力。 + +我们通过下面两张图对比下`BeanWrapperImpl`跟`DataBinder` + +1. `DataBinder` + +![](./image/2020033102.png) + +2. `BeanWrapperImpl` + +![](./image/2020033103.png) + +可以发现跟`BeanWrapperImpl`不同的是,它并没有通过继承某一个类来实现类型转换,而是通过组合的方式(`DataBinder`持有一个`SimpleTypeConverter`的引用,通过这个`SimpleTypeConverter`完成了类型转换) + +## 使用示例 + +```java +public class Main { + public static void main(String[] args) throws BindException { + Person person = new Person(); + DataBinder binder = new DataBinder(person, "person"); + // 创建用于绑定到对象上的属性对(属性名称,属性值) + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("name", "fsx"); + pvs.add("age", 18); + binder.bind(pvs); + System.out.println(person); + // 程序打印:Person{name='dmz', age=18} + } +} + +class Person { + String name; + + int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "Person{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } +} +``` + +>在上面的例子中要明确一点,Person中必须要提供setter方法(getter方法可以不提供,因为我们只是设置值),实际上`DataBinder`底层也是同样也是采用了Java的内省机制(关于Java的内省机制如果不了解的话,请参考《Spring官网阅读十四》),而内省只会根据setter方法以及getter来设置或者获取Bean中的属性。 + +## 源码分析 + +可能有细心的同学会发现,`DataBinder`是位于我们的`org.springframework.validation`包下的,也就是说它跟Spring中的校验也有关系,不过校验相关的内容不是我们本节要探讨的,本文我们只探讨`DataBinder`跟数据绑定相关的内容。 + +`DataBinder`所在的包结构如下: + +![](./image/2020033104.png) + +OK,明确了要分析的点之后,接下来我们就看看到底数据是如何绑定到我们的对象上去的,核心代码如下: + +### bind方法 + +第一步,我们是直接调用了bind方法来完成,其代码如下: + +```java +public void bind(PropertyValues pvs) { + MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ? + (MutablePropertyValues) pvs : new MutablePropertyValues(pvs)); + // 最终调用了doBind方法,如果大家对Spring代码有所了解的话,会发现Spring中有很多doXXX的方法 + // 形如doXXX这种命名方式的方法往往就是真正“干活”的代码,对于本例来说,肯定就是它来完成数据绑定的 + doBind(mpvs); +} +``` + +### doBind方法 + +```java +protected void doBind(MutablePropertyValues mpvs) { + // 校验 + checkAllowedFields(mpvs); + // 校验 + checkRequiredFields(mpvs); + // 真正进行数据绑定 + applyPropertyValues(mpvs); +} +``` + +跟校验相关的代码不在本文的探讨范围内,如果感兴趣的话可以关注我接下来的文章。我们现在把注意力放在`applyPropertyValues`这个方法,方法名直译过来的意思是--------应用属性值,就是将方法参数中的属性值应用到Bean上,也就是进行属性绑定。不知道大家看到这个方法名是否熟悉,如果对源码有一定了解的话,一定会知道Spring在完成属性注入的过程中调用了一个同名的方法,关于这个方法稍后我会带大家找一找然后做个比较,现在我们先看看`doBind`方法中`applyPropertyValues`干了什么 + +### applyPropertyValues方法 + +```java +protected void applyPropertyValues(MutablePropertyValues mpvs) { + try { + // 逻辑非常简单,获取一个属性访问器,然后直接通过属性访问器将属性值设置上去 + // IgnoreUnknownFields:忽略在Bean中找不到的属性 + // IgnoreInvalidFields:忽略找到,但是没有访问权限的值 + getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields()); + } + catch (PropertyBatchUpdateException ex) { + // 省略部分代码..... + } +} +``` + +这段代码主要做了两件事 + +#### 获取一个属性访问器 + +`getPropertyAccessor()`,获取一个属性访问器,关于属性访问器在《Spring官网阅读十四》也有介绍,这里我再做一些补充 + +![](./image/2020033105.png) + +可以看到,`PropertyAccessor`(也就是我们所说的属性访问器)只有两个实现类 + +- 第一个,`BeanWrapperImpl` +- 第二个,`DirectFieldAccessor` + +那么这两个有什么区别呢?第一个我们已经知道了,它是基于内省来实现的,所以`BeanWrapperImpl`肯定是基于getter,setter方法来实现对属性的操作的。第二个从名字上我们可以猜测,它估计是直接通过反射来获取字段的,也就是说,不需要提供setter/getter方法。大家可以自行做个测试,这里我就直接给结论了 + +- `BeanWrapperImpl`,基于内省,依赖getter/setter方法 +- `DirectFieldAccessor`,基于反射,不需要提供getter/setter方法 + +那么接下来,我们思考一个问题,`DataBinder`中的`getPropertyAccessor()`访问的是哪种类型的属性访问器呢?其实结合我们之前那个使用的示例就很容易知道,它肯定返回的是一个基于内省机制实现的属性访问器,并且它就是返回了一个`BeanWrapperImpl`。代码如下: + +```java +// 1.获取一个属性访问器,可以看到,是通过getInternalBindingResult()方法返回的一个对象来获取的 +// 那么getInternalBindingResult()做了什么呢? +protected ConfigurablePropertyAccessor getPropertyAccessor() { + return getInternalBindingResult().getPropertyAccessor(); +} + +// 2.getInternalBindingResult()又调用了一个initBeanPropertyAccess(),从名字上来看,就是用来初始化属性访问器的,再看看这个方法干了啥 +protected AbstractPropertyBindingResult getInternalBindingResult() { + if (this.bindingResult == null) { + initBeanPropertyAccess(); + } + return this.bindingResult; +} + +// 3.调用了一个createBeanPropertyBindingResult,创建了一个对象,也就是通过创建的这个对象返回了一个属性访问器,那么这个对象是什么呢?接着往下看 +public void initBeanPropertyAccess() { + Assert.state(this.bindingResult == null, + "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods"); + this.bindingResult = createBeanPropertyBindingResult(); +} + +// 4.可以发现创建的这个对象就是一个BeanPropertyBindingResult +protected AbstractPropertyBindingResult createBeanPropertyBindingResult() { + BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(), + getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit()); + // ..... + return result; +} + +// 5.跟踪这个对象的getPropertyAccessor()方法,发现就是返回了一个beanWrapper +// 现在明朗了吧,dataBinder最终也是依赖于beanWrapper +public final ConfigurablePropertyAccessor getPropertyAccessor() { + if (this.beanWrapper == null) { + this.beanWrapper = createBeanWrapper(); + this.beanWrapper.setExtractOldValueForEditor(true); + this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths); + this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit); + } + return this.beanWrapper; +} +``` + +> 我们可以思考一个问题,为什么Spring在实现数据绑定的时候不采用`DirectFieldAccessor`而是`BeanWrapperImpl`呢?换言之,为什么不直接使用反射而使用内省呢? +> +> 我个人的理解是:反射容易打破Bean的封装性,基于内省更安全。Spring在很多地方都不推荐使用反射的方式,比如我们在使用@Autowired注解进行字段注入的时候,编译器也会提示,”Field injection is not recommended “,不推荐我们使用字段注入,最好将@Autowired添加到setter方法上。 + +#### 通过属性访问器直接set属性值 + +> 这段代码十分繁琐,如果不感兴趣可以直接跳过,整个核心就是获取到对象中的setter方法,然后反射调用。 + +##### 1、setPropertyValues + +此方法位于`org.springframework.beans.AbstractPropertyAccessor#setPropertyValues(org.springframework.beans.PropertyValues, boolean, boolean)` + +```java +public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) + throws BeansException { + + List propertyAccessExceptions = null; + List propertyValues = (pvs instanceof MutablePropertyValues ? + ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues())); + for (PropertyValue pv : propertyValues) { + try { + // 核心代码就是这一句 + setPropertyValue(pv); + } + // ...... + } +``` + +##### 2、setPropertyValue(String,Object) + +此方法位于`org.springframework.beans.AbstractNestablePropertyAccessor#setPropertyValue(java.lang.String, java.lang.Object)` + +```java +public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException { + AbstractNestablePropertyAccessor nestedPa; + try { + // 这里是为了解决嵌套属性的情况,比如一个person对象中,包含一个dog对象,dog对象中有一个name属性 + // 那么我们可以通过dog.name这种方式来将一个名字直接绑定到person中的dog上 + // 与此同时,我们不能再使用person的属性访问器了,因为使用dog的属性访问器,这里就是返回dog的属性访问器 + nestedPa = getPropertyAccessorForPropertyPath(propertyName); + } + // ....... + + // PropertyTokenHolder是什么呢?例如我们的Person对象中有一个List name的属性, + // 那么我们在绑定时,需要对List中的元素进行赋值,所有我们会使用name[0],name[1]这种方式来进行绑定, + // 而PropertyTokenHolder中有三个属性,其中actualName代表name,canonicalName代表整个表达式name[0],而key则代表0这个下标位置 + PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName)); + // 最后通过属性访问器设置值 + nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value)); +} +``` + +对上面的结论进行测试,测试代码如下: + +```java +public class Main { + public static void main(String[] args) throws BindException { + Person person = new Person(); + DataBinder binder = new DataBinder(person, "person"); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("dog.dogName","dawang"); + pvs.add("name[0]", "dmz0"); + pvs.add("name[1]", "dmz1"); + pvs.add("age", 18); + binder.bind(pvs); + System.out.println(person); + } +} + +class Dog { + // 省略getter/setter方法 + String dogName; +} + +class Person { + // 省略getter/setter方法 + List name; + Dog dog; + int age; +} +``` + +在方法的如下位置添加条件断点(`propertyName.equals("dog.dogName")`): + +![](./image/2020033106.png) + +启动main方法,并开始调试,程序进入如下结果: + +![](./image/2020033107.png) + +我们关注红框标注的三个位置 + +1. 第一个红框,标注了当前属性访问器所对应的对象为Dog +2. 第二个红框,这是一个特殊的`AbstractNestablePropertyAccessor`,专门用于处理嵌套属性这种情况的,所以它包含了嵌套的路径 +3. 第三个红框,标注了这个嵌套的属性访问器的根对象是Person + +同样的,按照这种方式我们也可以对Person中的`List name`属性进行调试,可以发现`PropertyTokenHolder`就是按照上文所说的方式进行存储数据的,大家可以自行调试,我这里就不在演示了。 + +##### 3、setPropertyValue(PropertyTokenHolder,PropertyValue) + +这个方法是对上面方法的重载,其代码仍然位于`org.springframework.beans.AbstractNestablePropertyAccessor`中,代码如下: + +```java +protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { + if (tokens.keys != null) { + // 前面已经说过了,keys其实就是下标数组,如果你能看到这里的话,肯定会有一个疑问,为什么需要一个数组呢?考虑这种属性List> list,这个时候为了表示它,是不是就要list[0][0]这种方式了呢?这个时候就需要用数组存储了,因为一个属性需要多个下标表示 + processKeyedProperty(tokens, pv); + } + else { + // 我们关注这个方法即可,解析完PropertyTokenHolder后,最终都要调用这个方法 + processLocalProperty(tokens, pv); + } +} +``` + +##### 4、processLocalProperty + +代码位于:`org.springframework.beans.AbstractNestablePropertyAccessor#processLocalProperty` + +```java + private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) { + PropertyHandler ph = getLocalPropertyHandler(tokens.actualName); + // .... 省略部分代码 + Object oldValue = null; + try { + Object originalValue = pv.getValue(); + Object valueToApply = originalValue; + // 判断成立,代表需要进行类型转换,conversionNecessary为null或者为true都成立 + if (!Boolean.FALSE.equals(pv.conversionNecessary)) { + // 判断成立,代表已经转换过了 + if (pv.isConverted()) { + valueToApply = pv.getConvertedValue(); + } + else { + if (isExtractOldValueForEditor() && ph.isReadable()) { + try { + oldValue = ph.getValue(); + } + // .... 省略部分代码 + } + // 类型转换的部分,之前已经分析过了,这里就没什么好讲的了 + valueToApply = convertForProperty( + tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor()); + } + pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); + } + // 核心代码就这一句 + ph.setValue(valueToApply); + } + // .... 省略部分代码 + } + } +``` + +##### 5、setValue + +代码位置:`org.springframework.beans.BeanWrapperImpl.BeanPropertyHandler#setValue` + +最终进入到`BeanWrapperImpl`中的一个内部类`BeanPropertyHandler`中,方法代码如下: + +```java +public void setValue(final @Nullable Object value) throws Exception { + final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ? + ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() : + this.pd.getWriteMethod()); + // .... 省略部分代码 + ReflectionUtils.makeAccessible(writeMethod); + writeMethod.invoke(getWrappedInstance(), value); + } +} +``` + +代码就是这么的简单,内省获取这个属性的`writeMethod`,其实就是`setter`方法,然后直接反射调用 + +------ + +在了解了`DataBinder`之后,我们再来学习跟基于`DataBinder`实现的子类 + +# DataBinder的子类 + +## 子类概览 + +![](./image/2020033108.png) + +可以看到`DataBinder`的直接子类只有一个`WebDataBinder`,从名字上我们就能知道,这个类主要作用于Web环境,从而也说明了数据绑定主要使用在Web环境中。 + +### WebDataBinder + +> 这个接口是为了Web环境而设计的,但是并不依赖任何的`Servlet API`。它主要的作用是作为一个基类让其它的类继承,例如`ServletRequestDataBinder`。 + +#### 代码分析 + +```java +public class WebDataBinder extends DataBinder { + + // 这两个字段的详细作用见下面的两个方法checkFieldDefaults/checkFieldMarkers + public static final String DEFAULT_FIELD_MARKER_PREFIX = "_"; + public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!"; + @Nullable + private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX; + @Nullable + private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX; + + // ......省略构造方法及一些getter/setter方法 + + @Override + protected void doBind(MutablePropertyValues mpvs) { + checkFieldDefaults(mpvs); + checkFieldMarkers(mpvs); + // 没有对数据绑定做什么扩展,只是单纯的调用了父类的方法,也就是DataBinder的方法 + super.doBind(mpvs); + } + + + // 若你给定的PropertyValue的属性名是以!开头的,例如,传入的属性名称为:!name,属性值为:dmz + // 那就做处理如下: + // 如果Bean中的name属性是可写的并且mpvs不存在name属性,那么向mpvs中添加一个属性对,其中属性名称为name,值为dmz + // 然后将!name这个属性值对从mpvs中移除 + // 其实这里就是说你可以使用!来给个默认值。比如!name表示若找不到name这个属性的时,就取它的值, + // 也就是说你request里若有穿!name保底,也就不怕出现null值啦 + protected void checkFieldDefaults(MutablePropertyValues mpvs) { + String fieldDefaultPrefix = getFieldDefaultPrefix(); + if (fieldDefaultPrefix != null) { + PropertyValue[] pvArray = mpvs.getPropertyValues(); + for (PropertyValue pv : pvArray) { + if (pv.getName().startsWith(fieldDefaultPrefix)) { + String field = pv.getName().substring(fieldDefaultPrefix.length()); + // 属性可写,并且当前要绑定的属性值中不包含这个去除了“!”的属性名 + if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) { + // 添加到要绑定到Bean中的属性值集合里 + mpvs.add(field, pv.getValue()); + } + mpvs.removePropertyValue(pv); + } + } + } + } + + // 处理_的步骤 + // 若传入的字段以“_”开头,以属性名称:“_name”,属性值dmz为例 + // 如果Bean中的name字段可写,并且mpvs没有这个值 + // 那么对Bean中的name字段赋默认的空值,比如Boolean类型默认给false,数组给空数组[],集合给空集合,Map给空map + // 然后移除mpvs中的“_name” + // 相当于说,当我们进行数据绑定时,传入“_name”时,如果没有传入具体的属性值,Spring会为我们赋默认的空值 + // 前提是必须以“_”开头 + protected void checkFieldMarkers(MutablePropertyValues mpvs) { + String fieldMarkerPrefix = getFieldMarkerPrefix(); + if (fieldMarkerPrefix != null) { + PropertyValue[] pvArray = mpvs.getPropertyValues(); + for (PropertyValue pv : pvArray) { + if (pv.getName().startsWith(fieldMarkerPrefix)) { + String field = pv.getName().substring(fieldMarkerPrefix.length()); + if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) { + Class fieldType = getPropertyAccessor().getPropertyType(field); + mpvs.add(field, getEmptyValue(field, fieldType)); + } + mpvs.removePropertyValue(pv); + } + } + } + } + + @Nullable + protected Object getEmptyValue(String field, @Nullable Class fieldType) { + return (fieldType != null ? getEmptyValue(fieldType) : null); + } + + // 根据不同的类型给出空值 + @Nullable + public Object getEmptyValue(Class fieldType) { + try { + // 布尔值,默认false + if (boolean.class == fieldType || Boolean.class == fieldType) { + return Boolean.FALSE; + } + // 数组,默认给一个长度为0的符合要求的类型的数组 + else if (fieldType.isArray()) { + return Array.newInstance(fieldType.getComponentType(), 0); + } + // 集合,也是给各种空集合,Set/List等等 + else if (Collection.class.isAssignableFrom(fieldType)) { + return CollectionFactory.createCollection(fieldType, 0); + } + else if (Map.class.isAssignableFrom(fieldType)) { + return CollectionFactory.createMap(fieldType, 0); + } + } + catch (IllegalArgumentException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Failed to create default value - falling back to null: " + ex.getMessage()); + } + } + // Default value: null. + return null; + } + + // 这个方法表示,支持将文件作为属性绑定到对象的上 + protected void bindMultipart(Map> multipartFiles, MutablePropertyValues mpvs) { + multipartFiles.forEach((key, values) -> { + if (values.size() == 1) { + MultipartFile value = values.get(0); + if (isBindEmptyMultipartFiles() || !value.isEmpty()) { + mpvs.add(key, value); + } + } + else { + mpvs.add(key, values); + } + }); + } + +} +``` + +可以看到相对于父类`DataBinder`,它主要做了以下三点增强 + +1. 可以手动为Bean中的属性提供默认值(提供“!”开头的属性名称) +2. 可以让容器对属性字段赋上某些空值(提供“_”开头的属性名称) +3. 可以将文件绑定到Bean上 + +#### 使用示例 + +```java +public class WebDataBinderMain { + public static void main(String[] args) { + A a = new A(); + WebDataBinder webDataBinder = new WebDataBinder(a); + MutablePropertyValues propertyValues = new MutablePropertyValues(); + // propertyValues.add("name","I AM dmz"); + propertyValues.add("!name","dmz"); + propertyValues.add("_list","10"); + webDataBinder.bind(propertyValues); + System.out.println(a); + // 程序打印: + // A{name='dmz', age=0, multipartFile=null, list=[], no_list=null} + // 如果注释打开,程序打印:A{name='I AM dmz', age=0, multipartFile=null, list=[], no_list=null} + } +} + +// 省略getter/setter方法 +class A{ + String name; + int age; + MultipartFile multipartFile; + List list; + List no_list; + } +} +``` + +### ServletRequestDataBinder + +> 相比于父类,明确的依赖了`Servlet API`,会从`ServletRequest`中解析出参数,然后绑定到对应的Bean上,同时还能将文件对象绑定到Bean上。 + +#### 代码分析 + +```java +public class ServletRequestDataBinder extends WebDataBinder { + + public void bind(ServletRequest request) { + // 从request中解析除MutablePropertyValues,用于后面的数据绑定 + MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request); + + // 如果是一个MultipartRequest,返回一个MultipartRequest + // 上传文件时,都是使用MultipartRequest来封装请求 + MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class); + + // 说明这个请求对象是一个MultipartRequest + if (multipartRequest != null) { + + // 调用父类方法绑定对应的文件 + bindMultipart(multipartRequest.getMultiFileMap(), mpvs); + } + // 留给子类扩展使用 + addBindValues(mpvs, request); + + // 调用WebDataBinder的doBind方法进行数据绑定 + doBind(mpvs); + } + + protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) { + } + //....... 省略部分代码 + +} +``` + +### ExtendedServletRequestDataBinder + +#### 代码分析 + +```java +public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder { + // ....省略构造方法 + + // 这个类在ServletRequestDataBinder复写了addBindValues方法,在上面我们说过了,本身这个方法也是ServletRequestDataBinder专门提供了用于子类复写的方法 + @Override + @SuppressWarnings("unchecked") + protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) { + String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; + // 它会从request获取名为HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE的属性 + // 我们在使用@PathVariable的时候,解析出来的参数就放在request中的这个属性上,然后由ExtendedServletRequestDataBinder完成数据绑定 + Map uriVars = (Map) request.getAttribute(attr); + if (uriVars != null) { + uriVars.forEach((name, value) -> { + if (mpvs.contains(name)) { + if (logger.isWarnEnabled()) { + logger.warn("Skipping URI variable '" + name + + "' because request contains bind value with same name."); + } + } + else { + mpvs.addPropertyValue(name, value); + } + }); + } + } + +} +``` + +### WebExchangeDataBinder + +> 这个绑定器用于web-flux响应式编程中,用于完成Mono类型的数据的绑定,最终绑定的动作还是调用的父类的doBind方法 + +### MapDataBinder + +> 它位于`org.springframework.data.web`是和Spring-Data相关,专门用于处理`target`是`Map`类型的目标对象的绑定,它并非一个public类,Spring定义的用于内部使用的类 + +### WebRequestDataBinder + +> 它是用于处理Spring自己定义的`org.springframework.web.context.request.WebRequest`的,旨在处理和容器无关的web请求数据绑定 + +# 总结 + +上面关于Web相关的数据绑定我没有做详细的介绍,毕竟当前的学习阶段的重点是针对Spring-Framework,对于Web相关的东西目前主要以了解为主,后续在完成SpringMVC相关文章时会对这部分做详细的介绍。 + +本文主要介绍了DataBinder的整个体系,重点学习了它的数据绑定相关的知识,但是不要忘记了,它本身也可以实现类型转换的功能。实际上,我们也可以这样理解,之所以要让DataBinder具备类型转换的能力,正是为了更好的完成数据绑定。 + +前文我们也提到了,DataBinder位于`org.springframework.validation`,所以它必定跟校验有关,具体有什么关系呢?下篇文章将详细介绍及分析Spring中的数据校验,它也将是整个SpringFramwork官网阅读笔记的最后一篇文章! \ No newline at end of file diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033101.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033101.png" new file mode 100644 index 0000000000000000000000000000000000000000..563be8d0de3e72c5160f89fa78e3b3d51af9b7bf Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033101.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033102.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033102.png" new file mode 100644 index 0000000000000000000000000000000000000000..1df77a8f45d8e64a8a30d9bb0ff2e39fa9ecb791 Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033102.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033103.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033103.png" new file mode 100644 index 0000000000000000000000000000000000000000..f61efca30b18c95c82a8a420994242bbf4a9a23c Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033103.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033104.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033104.png" new file mode 100644 index 0000000000000000000000000000000000000000..96c165f4c6379bf0dac3032afb77e7ccc11b1895 Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033104.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033105.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033105.png" new file mode 100644 index 0000000000000000000000000000000000000000..08dc79411ce480d7dc7d19a0ae75dcf6a8455ccd Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033105.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033106.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033106.png" new file mode 100644 index 0000000000000000000000000000000000000000..f890ad6c5d3ba6248112b0640c909ecd2e172c56 Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033106.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033107.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033107.png" new file mode 100644 index 0000000000000000000000000000000000000000..50061ba4c6ac1f99ec10645e7b0e9f8a5c43adb5 Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033107.png" differ diff --git "a/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033108.png" "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033108.png" new file mode 100644 index 0000000000000000000000000000000000000000..409ab374e24effb4bed11e72d2f9ac90386614a5 Binary files /dev/null and "b/docs/Spring/Spring\345\256\230\347\275\221\345\255\246\344\271\240\347\254\224\350\256\260/Spring\345\256\230\347\275\221\351\230\205\350\257\273\357\274\210\345\215\201\345\205\255\357\274\211Spring\344\270\255\347\232\204\346\225\260\346\215\256\347\273\221\345\256\232/image/2020033108.png" differ diff --git "a/docs/Spring/Spring\346\235\202\350\260\210/\344\273\216java\347\232\204Type\345\210\260Spring\347\232\204ResolvableType/\344\273\216java\347\232\204Type\345\210\260Spring\347\232\204ResolvableType.md" "b/docs/Spring/Spring\346\235\202\350\260\210/\344\273\216java\347\232\204Type\345\210\260Spring\347\232\204ResolvableType/\344\273\216java\347\232\204Type\345\210\260Spring\347\232\204ResolvableType.md" index b53e49467750215bb7c3616eb8cee70947843bca..13109e6b6799a9ce5afbe2e78b960131fef186b0 100644 --- "a/docs/Spring/Spring\346\235\202\350\260\210/\344\273\216java\347\232\204Type\345\210\260Spring\347\232\204ResolvableType/\344\273\216java\347\232\204Type\345\210\260Spring\347\232\204ResolvableType.md" +++ "b/docs/Spring/Spring\346\235\202\350\260\210/\344\273\216java\347\232\204Type\345\210\260Spring\347\232\204ResolvableType/\344\273\216java\347\232\204Type\345\210\260Spring\347\232\204ResolvableType.md" @@ -8,15 +8,17 @@ Type是Java 编程语言中所有类型的公共高级接口(官方解释),也就是Java中所有类型的“爹”;其中,“所有类型”的描述尤为值得关注。它并不是我们平常工作中经常使用的 int、String、List、Map等数据类型,而是从Java语言角度来说,对基本类型、引用类型向上的抽象; -Type体系中类型的包括:原始类型(Class)、参数化类型(ParameterizedType)、数组类型(GenericArrayType)、类型变量(TypeVariable)、基本类型(Class); +Type体系中类型的包括:Class类型(原始类型,基本类型)、参数化类型(ParameterizedType)、数组类型(GenericArrayType)、类型变量(TypeVariable); 原始类型,不仅仅包含我们平常所指的类,还包括枚举、数组、注解等; -参数化类型,就是我们平常所用到的泛型List、Map这种; +基本类型,也就是我们所说的java的基本类型,即int,float,double等 -数组类型,并不是我们工作中所使用的数组String[] 、byte[],而是带有泛型的数组,即T[] ; +参数化类型,就是我们平常所用到的泛型`List`、`Map`这种; -基本类型,也就是我们所说的java的基本类型,即int,float,double等 +类型变量,就是我们在定义泛型时使用到的T,U,K这些,例如`Person`,这里的T就是类型变量 + +数组类型,并不是我们工作中所使用的数组String[] 、byte[],而是参数化类型或者类型变量的数据,即T[] ,或者`List[]`; **Type体系的出现主要是为了解决泛型的一系列问题。**