JSR303数据校验与自动配置原理

文章目录

  • JSR303数据校验
    • 1、JSR303数据校验
      • 1.1 给需要校验的字段添加校验注解
      • 1.2 给需要检验的方法标准@Valid
      • 1.3 捕捉校验异常,返回提示信息
      • 1.4 分组校验(多场景的复杂校验)
      • 1.5. 自定义校验
    • 2、多环境切换
  • 自动配置原理
    • 1、分析自动配置原理
    • 2、精髓
    • 3、@Conditional【了解】
    • 4、小结

JSR303数据校验

1、JSR303数据校验

定义

JSR303技术,JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint,在springboot中使用也比较简便。

先看看如何使用

Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;

@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @Email(message="邮箱格式错误") //name必须是邮箱格式
    private String name;
}

运行结果 :default message [不是一个合法的电子邮件地址];

使用数据校验,可以保证数据的正确性;

常见参数

@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.
    
Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
    
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.

日期检查
@Past       验证 DateCalendar 对象是否在当前时间之前  
@Future     验证 DateCalendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则

1.1 给需要校验的字段添加校验注解

javax.validation.constraints包下有许多的注解:
JSR303数据校验与自动配置原理_第1张图片

常用的校验注解补充:

@NotBlank检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty检查约束元素是否为NULL或者是EMPTY.
@Length被检查的字符串长度是否在指定的范围内
@CreditCardNumber信用卡验证
@Email验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@URL 验证是否是一个url地址

注意:一个字段可以标注多个校验注解。

1.2 给需要检验的方法标准@Valid

JSR303数据校验与自动配置原理_第2张图片

如果只标注了注解字段,不启用@valid的是不生效的。

1.3 捕捉校验异常,返回提示信息

对于程序可能有很多的校验注解,可能会出现多个校验错误,我们可以定义一个统一的异常处理类,帮我们捕捉校验错误并返回提示信息,这里可以利用Spring的ControllerAdvice技术。

(1)编写统一异常处理类

package io.renren.app.exception;import io.renren.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;import java.util.HashMap;
import java.util.Map;/**
 * @Description 统一异常处理
 */
@RestControllerAdvice(basePackages = "io.renren.app.controller")
@Slf4j
public class AppManageControllerAdvices {@ExceptionHandler(value = MethodArgumentNotValidException.class)
 public R handleVaildException(MethodArgumentNotValidException e) {
 log.error("数据校验出现问题{},异常类型:{}", e.getMessage(), e.getClass());
 BindingResult bindingResult = e.getBindingResult();Map<String, String> errorMap = new HashMap<>();
 bindingResult.getFieldErrors().forEach((fieldError) -> {
 errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
 });
 return R.error(400, "数据校验失败").put("data", errorMap);
 }@ExceptionHandler(value = Throwable.class)
 public R handleException(Throwable throwable) {
​
 log.error("错误:", throwable);
 return R.error(500, "系统未知异常");
 }
}

(2)出现异常将异常抛出
对于校验可能会出现的异常,我们将其抛出,不予捕捉感知,都交给我们的统一异常处理类处理,返回提示信息。
JSR303数据校验与自动配置原理_第3张图片

1.4 分组校验(多场景的复杂校验)

对于不同的操作,字段校验的规则和数量可能是不同的,所以我们将校验规则分组,对于不同的操作进行不同的校验组,使用groups属性。

1.想要使用分组校验功能,根据文档我们首先编写不同的校验组接口,只编写空接口,用来表示就可以了:

//新增分组
public interface AddGroup{}
//修改分组
public interface UpdateGroup{}

2.编写好分组接口,对于不同的检验规则,标注不同的分组标识:

@NotNull ( message ="修改必须指定id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@TableId
private Long Id;@NotBlank(message="名称必须提交",groups={AddGroup.class,UpdateGroup.class})
private String name;

3.在controller方法上标注不同的分组校验,使用@Validated注解:

 //保存
 @RequestMapping("/save")
 //@RequiresPermissions("product:brand:save")
 public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
 brandService.save(brand);
 return R.ok();
 }

@Validated({AddGroup.class}):启用不同的分组校验规则。

注意:在使用分组校验的情况下,对于没有标注分组的校验规则,默认是不生效的,只有标注了分组的校验规则才会生效。

1.5. 自定义校验

1.编写一个自定义的校验注解

导入依赖:

<dependency>
 <groupId>javax.validationgroupId>
 <artifactId>validation-apiartifactId>
 <version>2.0.1.Finalversion>
dependency>

编写自定义校验注解

package com.ztb.common.valid;import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;@Documented
//指定校验器
@Constraint(validatedBy = {com.ztb.common.valid.ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
 //配置默认的提示消息,新建配置文件ValidationMessages.properties在其中配置:
 //com.ztb.common.valid.ListValue.message = 必须提交指定的值
 String message() default="{com.ztb.common.valid.ListValue.message}";

 Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };int[] vals() default { };
}

用来验证自定义的字段值,非0即1。

2.编写一个自定义的校验器

package com.ztb.common.valid;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {private Set<Integer> set = new HashSet<Integer>();
 //初始化方法
 public void initialize(ListValue constraintAnnotation) {
 int[] vals = constraintAnnotation.vals();
 for (int val : vals) {
 set.add(val);
 }
 }
 //判断是否校验成功
 /**
 * @param value 需要校验的值
 * @param context
 * @return
 */
 public boolean isValid(Integer value, ConstraintValidatorContext context) {
 return set.contains(value);
 }
}

3.关联自定义的校验器和自定义的校验注解

@Constraint(validatedBy = {com.ztb.common.valid.ListValueConstraintValidator.class })

在自定义的校验注解中添加自己的校验器,就关联好了,一个校验注解可以指定多个不同类型的校验器,适配不同类型的校验。关联完成就可以使用了。

4.使用

//校验指定字段非0即1
 @ListValue(valus = {0,1},message="提示消息,不指定则默认读取配置文件中的")
 private Integer showStatus;

测试:

save方法传参: showStatus: 3 返回:
JSR303数据校验与自动配置原理_第4张图片
自定义校验注解生效。

2、多环境切换

profileSpring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;

多配置文件

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml, 用来指定多个环境版本;

例如:

  • application-test.properties 代表测试环境配置
  • application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;

我们需要通过一个配置来选择需要激活的环境:

#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

yaml的多文档块

properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !

server:
  port: 8081
#选择要激活那个环境块
spring:
  profiles:
    active: prod
    
---
server:
  port: 8083
spring:
  profiles: dev #配置环境的名称

---

server:
  port: 8084
spring:
  profiles: prod  #配置环境的名称

注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!

配置文件加载位置

JSR303数据校验与自动配置原理_第5张图片

Spring boot的默认配置文件:

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件

优先级由高到底,高优先级的配置会覆盖低优先级的配置;

SpringBoot会从这四个位置全部加载主配置文件;互补配置;

拓展:

指定位置加载配置文件

我们还可以通过spring.config.location来改变默认的配置文件位置

相同配置,外部指定的配置文件优先级最高

   java -jar spring-boot-config.jar --spring.config.location=F:/application.properties

自动配置原理

1、分析自动配置原理

我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration

//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到 IOC 容器中
@EnableConfigurationProperties({HttpProperties.class})

//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = Type.SERVLET)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled
//如果不存在,判断也是成立的
//即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "spring.http.encoding", value = {"enabled"},matchIfMissing = true)

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration 

//启动指定类的ConfigurationProperties功能;
  //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
  //并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

//Spring底层@Conditional注解
  //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
  //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = Type.SERVLET)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
  //如果不存在,判断也是成立的
  //即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "spring.http.encoding", value = {"enabled"},matchIfMissing = true)

public class HttpEncodingAutoConfiguration {
    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //。。。。。。。
}

总结 :根据当前不同的条件判断,决定这个配置类是否生效!

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {
    // .....
}

2、精髓

1、SpringBoot启动会加载大量的自动配置类

2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

  • xxxxAutoConfigurartion:自动配置类;给容器中添加组件
  • xxxxProperties:封装配置文件中相关属性;

3、@Conditional【了解】

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
JSR303数据校验与自动配置原理_第6张图片

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

我们怎么知道哪些自动配置类生效?

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug=true

Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)

4、小结

自动装配
springboot到底帮我们配置了什么?我们能不能进行修改?能修改哪些东西?能不能扩展?
xxxxAutoConfiguraion..向容器中自动配置组件
xxxxProperties:自动配置类,装配配置文件中自定义的一些内容!

你可能感兴趣的:(spring,boot,spring,boot,后端,java)