需求前言
无论是对于前端发起调用接口,抑或是内部RPC等方式调用接口,对于一个下游接口来说,众所周知,毫无疑问,一开始要做的肯定是对入参进行校验,但直接在controller写校验,或者封装一个校验方法(将多种类型的校验,如判空、判长度等),都违背了设计模式的单一职责,显得太不优雅。
文本将介绍使用模板方法模式+责任链这两种设计模式,优雅地解决入参校验问题!
因本文重要是介绍如何解决入参,故而模板方法模式和责任链模式是简单介绍
模板方法模式
模板方法模式是一种行为型设计模式,他在一个抽象类中定义一个算法(业务逻辑)的骨架,具体步骤的实现由子类提供,它通过将算法的不变部分放在抽象类中,可变部分放在子类中,让子类重写,达到代码的复用和扩展。
责任链模式
也是一种行为型设计模式,将多个对象串联成一条链,一个请求沿着该链路,让多个对象串行执行请求,直到最后一个对象为止。
它主要避免了请求发送者和接受者之间的耦合,目的是增强系统的灵活性和扩展性;
模板方法模式+责任链模式实现入参校验演示
①
/**
* @date: 2025/1/2 18:39
* @author: lvan
* @description: 校验器枚举类,维护下一个校验器的关系
*/
public enum ValidatRelationEnum {
NOT_NULL("notNull","判断是否为空校验器","age"),
AGE("age","判断年龄校验器",null);
private String TypeCode;
private String desc;
private String nextTypeCode;
ValidatRelationEnum(String typeCode, String desc, String nextTypeCode) {
TypeCode = typeCode;
this.desc = desc;
this.nextTypeCode = nextTypeCode;
}
public String getTypeCode() {
return TypeCode;
}
public void setTypeCode(String typeCode) {
TypeCode = typeCode;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getNextTypeCode() {
return nextTypeCode;
}
public void setNextTypeCode(String nextTypeCode) {
this.nextTypeCode = nextTypeCode;
}
}
定义校验器关系枚举类
/**
* @date: 2025/1/2 18:39
* @author: lvan
* @description: 校验器接口
*/
public interface ValidatorInteface {
/**
* 校验器校验方法
*
* @param requestDto 接口请求入参
*/
void validat(RequestDto requestDto);
/**
* @return 当前校验器的类型
*/
ValidatRelationEnum getCurrentValidatorType();
/***
*
* @return 获取下一个校验器
*/
default ValidatorInteface getNextValidator() {
String nextTypeCode = getCurrentValidatorType().getNextTypeCode();
if (!StringUtils.isEmpty(nextTypeCode)) {
return ApplicationUtils.getBeanClass(nextTypeCode);
}
return null;
}
}
定义校验器接口,定义的getNextValidator()是获取当前的下一个校验器,并与当前校验器建立关系;
②
public class AbstractValidator implements ValidatorInteface{
private ValidatorInteface nextValidator;
@Override
public void validat(RequestDto requestDto) {
//校验前
preValidat(requestDto);
//校验方法
doValidat(requestDto);
//校验后
postValidat(requestDto);
//如果有下一个校验器,就往下执行
if (nextValidator != null) {
nextValidator.validat(requestDto);
}
}
}
抽象类实现接口,实现的validat()使用了模板方法模式,定义了preValidate、doValidat步骤方法,真正的校验方法是doValidat(),最后判断当前校验器的下一个校验器不为空就继续调用,为空就退出
public void preValidat(RequestDto requestDto) {
throw new RuntimeException("校验前……");
}
在抽象类定义的步骤方法,为了让子类重写,可以通过抛异常的方式;
③
/**
* @date: 2025/1/2 19:43
* @author: lvan
* @description: 空值校验器
*/
@Primary
@Service("notNull")
public class NotNullValidator extends AbstractValidator{
@Override
public void preValidat(RequestDto requestDto) {
System.out.println("空值校验器校验前……");
}
@Override
public void doValidat(RequestDto requestDto) {
if(Objects.isNull(requestDto.getName())){
System.err.println("名称不能为空!");
throw new RuntimeException("名称不能为空!");
}
if(Objects.isNull(requestDto.getAge())){
throw new RuntimeException("年龄不能为空!");
}
}
@Override
public void postValidat(RequestDto requestDto) {
System.out.println("空值校验器校验后……");
}
@Override
public ValidatRelationEnum getCurrentValidatorType() {
//定义当前是空值校验器
return ValidatRelationEnum.NOT_NULL;
}
}
定义判空校验器,并通过getCurrentValidatorType()定义当前校验器的类型,便于在接口类ValidatorInteface#getNextValidator()获取当前校验器的下一个校验器;
/**
* @date: 2025/1/2 19:43
* @author: lvan
* @description: 年龄校验器
*/
@Service("age")
public class AgeValidator extends AbstractValidator {
@Override
public void preValidat(RequestDto requestDto) {
System.out.println("年龄校验器校验前……");
}
@Override
public void doValidat(RequestDto requestDto) {
if (requestDto.getAge() <= 25) {
System.err.println("年龄不能小于25岁!");
throw new RuntimeException("年龄不能小于25岁!");
}
}
@Override
public void postValidat(RequestDto requestDto) {
System.out.println("年龄校验器校验后……");
}
@Override
public ValidatRelationEnum getCurrentValidatorType() {
//定义当前是年龄校验器
return ValidatRelationEnum.AGE;
}
}
年龄校验器也是如此;
④
@Override
public void afterSingletonsInstantiated() {
//在启动spring容器时,就关联下一个校验器
ValidatorInteface nextValidator = getNextValidator();
this.nextValidator = nextValidator;
}
在抽象类中定义afterSingletonsInstantiated()用于在spring容器初始化时,建立当前校验器与下一个校验器的关系,是通过抽象类中的nextValidator进行关联;
⑤
/**
* @date: 2025/1/2 18:39
* @author: lvan
* @description: 校验链
*/
@Component
public class ValidatorChain {
public ValidatorInteface validatorInteface;
public ValidatorChain(ValidatorInteface validatorInteface) {
this.validatorInteface = validatorInteface;
}
public void validator(RequestDto requestDto) {
//默认从空值校验器开始调,当最后一个校验器的关系枚举类的next属性为空,就代表是最后一个
validatorInteface.validat(requestDto);
}
}
最后定义责任链,默认第一个调用的是空值校验器
演示结果
我上例中校验条件是
if(Objects.isNull(requestDto.getName())){
throw new RuntimeException("名称不能为空!");
}
if(Objects.isNull(requestDto.getAge())){
throw new RuntimeException("年龄不能为空!");
}
if (requestDto.getAge() <= 25) {
throw new RuntimeException("年龄不能小于25岁!");
}
校验通过的结果:
校验失败的结果
查看报错日志
因为我是打印
System.err.println(“年龄不能小于25岁!”);
还是挺简单的,完结撒花~
如有需要收藏的看官,顺便也用发财的小手点点赞哈,如有错漏,也欢迎各位在评论区评论!