如果配置文件没有相关的配置,那么** 启动时
**就会出现以下异常:
Description: Binding to target com.toby.provide.ApplicationProperties@31da6b2e failed: Property: feichao.info.name Value: null Reason: 名字不能为空,请注意检查,参考值为:肥朝。 复制代码
肥朝小声逼逼:提高代码稳壮性,肥朝认为最好的办法就是提前预防。实际项目中,我们在配置文件配置了各种参数。但是大家也知道,不同环境的配置参数,是会不一样的,难免会因为人为疏忽,导致某个环境的配置文件,少了一些关键参数,光靠肉眼来检查,必然是一个低效而又不可靠的方式。如果你不用该方式校验,很容易在某个特殊的场景下,才触发出坑。但是你采用这种方式,做了大量的启动时校验,一旦参数不合法,项目启动都启动不了,做到了防范于未然!
@Data public class HelloDTO { @NotBlank private String name; @Min(0) @Max(150) private int age; } 复制代码
@RestController public class HelloController { @RequestMapping("/controllerValid") public String controllerValid(@RequestBody @Valid HelloDTO helloDTO) { return "ok"; } } 复制代码
请求参数为
{ "name":"肥朝", "age" : 151 } 复制代码
时,出现校验异常
org.springframework.web.bind.MethodArgumentNotValidException 复制代码
@Data public class HelloDTO { @NotBlank private String name; @Min(0) @Max(150) private int age; } 复制代码
public interface AService { int insertUser(HelloDTO helloDTO); } 复制代码
@Service public class AServiceImpl implements AService { @Autowired private BService bService; @Override public int insertUser(HelloDTO helloDTO) { return bService.insertUser(helloDTO); } } 复制代码
public interface BService { int insertUser(@Valid HelloDTO helloDTO); } 复制代码
@Service @Slf4j @Validated public class BServiceImpl implements BService { @Override public int insertUser(HelloDTO helloDTO) { log.info("BService insertUser..."); return 0; } } 复制代码
@RunWith(SpringRunner.class) @SpringBootTest public class AserviceTest { @Autowired private BService bService; @Test public void testInsertUser() throws Exception { HelloDTO helloDTO = new HelloDTO(); bService.insertUser(helloDTO); } } 复制代码
输出校验异常信息:
javax.validation.ConstraintViolationException 复制代码
Dubbo官方文档中已经写得非常详细,地址为: dubbo.apache.org/zh-cn/docs/…
参数验证功能是基于 JSR303 实现的,用户只需标识 JSR303 标准的验证 annotation,并通过声明 filter 来实现验证。
<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0.GA</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>4.2.0.Final</version> </dependency> 复制代码
参数标注示例
import java.io.Serializable; import java.util.Date; import javax.validation.constraints.Future; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Past; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; public class ValidationParameter implements Serializable { private static final long serialVersionUID = 7158911668568000392L; @NotNull // 不允许为空 @Size(min = 1, max = 20) // 长度或大小范围 private String name; @NotNull(groups = ValidationService.Save.class) // 保存时不允许为空,更新时允许为空 ,表示不更新该字段 @Pattern(regexp = "^//s*//w+(?://.{0,1}[//w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*//.[a-zA-Z]+//s*$") private String email; @Min(18) // 最小值 @Max(100) // 最大值 private int age; @Past // 必须为一个过去的时间 private Date loginDate; @Future // 必须为一个未来的时间 private Date expiryDate; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getLoginDate() { return loginDate; } public void setLoginDate(Date loginDate) { this.loginDate = loginDate; } public Date getExpiryDate() { return expiryDate; } public void setExpiryDate(Date expiryDate) { this.expiryDate = expiryDate; } } 复制代码
分组验证示例
public interface ValidationService { // 缺省可按服务接口区分验证场景,如:@NotNull(groups = ValidationService.class) @interface Save{} // 与方法同名接口,首字母大写,用于区分验证场景,如:@NotNull(groups = ValidationService.Save.class),可选 void save(ValidationParameter parameter); void update(ValidationParameter parameter); } 复制代码
关联验证示例
import javax.validation.GroupSequence; public interface ValidationService { @GroupSequence(Update.class) // 同时验证Update组规则 @interface Save{} void save(ValidationParameter parameter); @interface Update{} void update(ValidationParameter parameter); } 复制代码
参数验证示例
import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; public interface ValidationService { void save(@NotNull ValidationParameter parameter); // 验证参数不为空 void delete(@Min(1) int id); // 直接对基本类型参数验证 } 复制代码
在客户端验证参数
<dubbo:reference id="validationService" interface="org.apache.dubbo.examples.validation.api.ValidationService" validation="true" /> 复制代码
在服务器端验证参数
<dubbo:service interface="org.apache.dubbo.examples.validation.api.ValidationService" ref="validationService" validation="true" /> 复制代码
验证异常信息
import javax.validation.ConstraintViolationException; import javax.validation.ConstraintViolationException; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.apache.dubbo.examples.validation.api.ValidationParameter; import org.apache.dubbo.examples.validation.api.ValidationService; import org.apache.dubbo.rpc.RpcException; public class ValidationConsumer { public static void main(String[] args) throws Exception { String config = ValidationConsumer.class.getPackage().getName().replace('.', '/') + "/validation-consumer.xml"; ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(config); context.start(); ValidationService validationService = (ValidationService)context.getBean("validationService"); // Error try { parameter = new ValidationParameter(); validationService.save(parameter); System.out.println("Validation ERROR"); } catch (RpcException e) { // 抛出的是RpcException ConstraintViolationException ve = (ConstraintViolationException) e.getCause(); // 里面嵌了一个ConstraintViolationException Set<ConstraintViolation<?>> violations = ve.getConstraintViolations(); // 可以拿到一个验证错误详细信息的集合 System.out.println(violations); } } } 复制代码
1.自 2.1.0
版本开始支持, 如何使用可以参考dubbo 项目中的示例代码( github.com/apache/incu… )
2.验证方式可扩展,扩展方式参见开发者手册中的验证扩展( dubbo.apache.org/zh-cn/docs/… )
public class ValidationUtils { private static Validator validator = Validation .byProvider(HibernateValidator.class) .configure() .failFast(true) .buildValidatorFactory() .getValidator(); public static <T> void validate(T obj) { Set<ConstraintViolation<T>> validate = validator.validate(obj); for (ConstraintViolation<T> violation : validate) { // 注意,在实际业务中 // 这里会根据violation进行参数拼接 // 然后进行自定义异常的抛出 } } } 复制代码
@Test public void testValidationUtils() throws Exception { HelloDTO helloDTO = new HelloDTO(); ValidationUtils.validate(helloDTO); } 复制代码
上述从五个方面,做了校验方式的示例,全都是基于 hibernate-validate
。但是细心的你就会发现,你搜索 hibernate-validate
,网上的基本只有** controller
**这种方案,因此肥朝从多个层来代码示例,基本覆盖你的全部场景。
另外需要注意的是,controller层抛出的校验异常类型是
org.springframework.web.bind.MethodArgumentNotValidException 复制代码
service层抛出的校验异常类型是
javax.validation.ConstraintViolationException 复制代码
工具类的方式校验时,需要拿到参数处理,再自定义异常抛出。因此在做全局异常的时候,需要注意。
当然,该校验方式不仅使得参数校验比
if (helloDTO.getAge() <= 0) { //... } 复制代码
优雅很多,还支持 自定义注解实现校验规则
、 国际化
、 分组校验
等,这些你们项目具体用到的时候,再去看看即可。
当然提高代码稳壮性的方式还有很多,但是肥朝认为参数校验的方式,成本和效果的性价比是最高之一,你有什么提高代码稳壮性的方式?留言告诉肥朝。