定义
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 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
在javax.validation.constraints
包下有许多的注解:
常用的校验注解补充:
@NotBlank
检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty
检查约束元素是否为NULL或者是EMPTY.
@Length
被检查的字符串长度是否在指定的范围内
@CreditCardNumber
信用卡验证
@Email
验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@URL
验证是否是一个url地址
注意:一个字段可以标注多个校验注解。
如果只标注了注解字段,不启用@valid的是不生效的。
对于程序可能有很多的校验注解,可能会出现多个校验错误,我们可以定义一个统一的异常处理类,帮我们捕捉校验错误并返回提示信息,这里可以利用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)出现异常将异常抛出
对于校验可能会出现的异常,我们将其抛出,不予捕捉感知,都交给我们的统一异常处理类处理,返回提示信息。
对于不同的操作,字段校验的规则和数量可能是不同的,所以我们将校验规则分组,对于不同的操作进行不同的校验组,使用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.编写一个自定义的校验注解
导入依赖:
<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 返回:
自定义校验注解生效。
profile
是Spring
对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;
多配置文件
我们在主配置文件编写的时候,文件名可以是 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配置文件的!
配置文件加载位置
Spring boot的默认配置文件:
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置;
拓展:
指定位置加载配置文件
我们还可以通过spring.config.location
来改变默认的配置文件位置
相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
我们以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 {
// .....
}
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxxAutoConfigurartion
:自动配置类;给容器中添加组件xxxxProperties
:封装配置文件中相关属性;了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional
派生注解(Spring注解版原生的@Conditional
作用)
作用:必须是@Conditional
指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
我们怎么知道哪些自动配置类生效?
我们可以通过启用 debug=true
属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类
debug=true
Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)
自动装配
springboot到底帮我们配置了什么?我们能不能进行修改?能修改哪些东西?能不能扩展?
xxxxAutoConfiguraion..
向容器中自动配置组件
xxxxProperties
:自动配置类,装配配置文件中自定义的一些内容!