责任链是设计模式中的一种,可以用一个例子来说明一下
现在有一个用户类
public class User {
private final String name;
private final Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
}
但是你想校验一下创建的用户合不合理,于是你创建了三个注解
规定名字的长度@Length
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Length {
int value();
}
规定年龄的最大值
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Max {
int value();
}
规定年龄的最小值
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Min {
int value();
}
那么我们就可以将代码进行改造,我们将最大值设置为17,最小值设置为20这样就一定会爆出异常,后面的分析的时候会说到这样设计的用处的
public class User {
@Length(3)
private final String name;
@Max(17)
@Min(20)
private final Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
}
那么我们就要让这些注解生效,所以我们就需要对应注解的校验器,先声明一个校验器接口然后让对应的校验器去继承这个接口
public interface ValidatorHandler {
void validate(Object value, ValidatorContext context);
}
长度校验器
public class LengthValidatorHandler implements ValidatorHandler {
private final int length;
public LengthValidatorHandler(int min) {
this.length = min;
}
@Override
public void validate(Object value, ValidatorContext context) {
if (value instanceof String stringValue) {
if (stringValue.length() != length) {
context.appendError("你的字符串长度是" + stringValue.length() + "应该是" + length);
}
}
}
}
最大值校验器
public class MaxValidatorHandler implements ValidatorHandler {
private final int max;
public MaxValidatorHandler(int max) {
this.max = max;
}
@Override
public void validate(Object value, ValidatorContext context) {
if (value instanceof Integer intValue) {
if (intValue > max) {
context.appendError("你的值是" + intValue + "不能大于" + max);
}
}
}
}
最小值校验器
public class MinValidatorHandler implements ValidatorHandler {
private final int min;
public MinValidatorHandler(int min) {
this.min = min;
}
@Override
public void validate(Object value, ValidatorContext context) {
if (value instanceof Integer intValue) {
if (intValue < min) {
context.appendError("你的值是" + intValue + "不能小于" + min);
}
}
}
}
然后我们可以定义一个总的校验器将这些校验器串联起来
public class Validator {
void validate(@NotNull Object bean) throws Exception {
// 获取对象的类对象,以便后续通过反射获取字段信息。
Class<?> beanClass = bean.getClass();
// 遍历类中所有声明的字段。
for (Field field : beanClass.getDeclaredFields()) {
// 设置字段为可访问状态,以便访问私有字段。
field.setAccessible(true);
// 获取字段上的 @Max 注解。
Max max = field.getAnnotation(Max.class);
// 如果字段上有 @Max 注解,则调用 validateMax 方法进行验证。
if (max != null) {
validateMax(max, field.get(bean));
}
// 获取字段上的 @Min 注解。
Min min = field.getAnnotation(Min.class);
// 如果字段上有 @Min 注解,则调用 validateMin 方法进行验证。
if (min != null) {
validateMin(min, field.get(bean));
}
// 获取字段上的 @Length 注解。
Length length = field.getAnnotation(Length.class);
// 如果字段上有 @Length 注解,则调用 validateLength 方法进行验证。
if (length != null) {
// 如果字段上有 @Length 注解,则调用 validateLength 方法进行验证。
validateLength(length, field.get(bean));
}
}
}
}
我们可以进一步去优化这个总的校验器
public class Validator {
void validate(@NotNull Object bean) throws Exception {
// 获取对象的类对象,以便后续通过反射获取字段信息。
Class<?> beanClass = bean.getClass();
// 遍历类中所有声明的字段。
for (Field field : beanClass.getDeclaredFields()) {
// 设置字段为可访问状态,以便访问私有字段。
field.setAccessible(true);
// 获取字段上的 @Max 注解。
Max max = field.getAnnotation(Max.class);
// 如果字段上有 @Max 注解,则调用 validateMax 方法进行验证。
if (max != null) {
new ValidatorHandler(max.value()).validate(field.get(bean));
}
// 获取字段上的 @Min 注解。
Min min = field.getAnnotation(Min.class);
// 如果字段上有 @Min 注解,则调用 validateMin 方法进行验证。
if (min != null) {
new ValidatorHandler(min.value()).validate(field.get(bean));
}
// 获取字段上的 @Length 注解。
Length length = field.getAnnotation(Length.class);
// 如果字段上有 @Length 注解,则调用 validateLength 方法进行验证。
if (length != null) {
// 如果字段上有 @Length 注解,则调用 validateLength 方法进行验证。
new ValidatorHandler(length.value()).validate(field.get(bean));
}
}
}
}
好的我们这样就实现了每次都是通过调用一个函数来进行校验的,那么我们就可以将这些校验的函数来组成一个校验链,这样每个字段都有自己的校验链,然后再校验的时候只需要调用这个校验链就行了
值得一提的是我们还需要创建一个上下文对象来存储过程中需要用到的上下文信息
public class ValidatorContext {
private final List<String> errorMessageList = new ArrayList<>();
//一个字段可能有多个校验器,具体校验器的下标
private int index = 0;
private Object value;
private Map<String, Object> data = new HashMap<>();
public ValidatorContext(Object value) {
this.value = value;
}
public void appendError(String message) {
errorMessageList.add(message);
}
public void stopChain() {
this.stop = true;
}
public void doNext(Object value) {
index++;
this.value = value;
}
public Object getValue() {
return value;
}
public int currentIndex() {
return index;
}
public void put(String key, Object value) {
this.data.put(key, value);
}
public Object get(String key) {
return this.data.get(key);
}
public void throwExceptionIfNecessary() throws ValidatorException {
if (errorMessageList.isEmpty()) {
return;
}
throw new ValidatorException(errorMessageList.toString());
}
}
我们就可以构建出这么一个校验链
public class ValidatorChain {
private final List<ValidatorHandler> handlers = new ArrayList<>();
public void validate(Object value) throws ValidatorException {
ValidatorContext context = new ValidatorContext(value);
while (true) {
int index = context.currentIndex();
if (index == handlers.size()) {
break;
}
ValidatorHandler handler = handlers.get(index);
handler.validate(context.getValue(), context);
if (index == context.currentIndex()) {
break;
}
}
context.throwExceptionIfNecessary();
}
public void addLastHandler(ValidatorHandler handler) {
this.handlers.add(handler);
}
}
然后我们要对总校验器进行改造
public class Validator {
private ValidatorChain buildValidateChain(Field field) {
ValidatorChain chain = new ValidatorChain();
Max max = field.getAnnotation(Max.class);
if (max != null) {
chain.addLastHandler(new MaxValidatorHandler(max.value()));
}
Min min = field.getAnnotation(Min.class);
if (min != null) {
chain.addLastHandler(new MinValidatorHandler(min.value()));
}
Length length = field.getAnnotation(Length.class);
if (length != null) {
chain.addLastHandler(new LengthValidatorHandler(length.value()));
}
return chain;
}
public void validate(Object bean) throws Exception {
Class<?> beanClass = bean.getClass();
for (Field field : beanClass.getDeclaredFields()) {
field.setAccessible(true);
ValidatorChain chain = buildValidateChain(field);
chain.validate(field.get(bean));
}
}
}
现在有点懵也没关系,我们来走一遍这个过程,我们定义一个main函数然后来走一下这个校验链的逻辑
public class Main {
public static void main(String[] args) throws Exception {
User user = new User("tom", 18);
Validator validator = new Validator();
validator.validate(user);
}
}
然后我们会进入Validator的这个函数中
public void validate(Object bean) throws Exception {
Class<?> beanClass = bean.getClass();
for (Field field : beanClass.getDeclaredFields()) {
field.setAccessible(true);
ValidatorChain chain = buildValidateChain(field);
chain.validate(field.get(bean));
}
}
然后就是对一个字段name进行责任链的构建,也就是下面这段代码,然后经过if判断max,min都是null,然后Length length = field.getAnnotation(Length.class)中可以得到name的length,然后将校验器添加到责任链当中去,那么这个字段name的责任链就只有一个校验器那就是:LengthValidatorHandler
private ValidatorChain buildValidateChain(Field field) {
ValidatorChain chain = new ValidatorChain();
Max max = field.getAnnotation(Max.class);
if (max != null) {
chain.addLastHandler(new MaxValidatorHandler(max.value()));
}
Min min = field.getAnnotation(Min.class);
if (min != null) {
chain.addLastHandler(new MinValidatorHandler(min.value()));
}
Length length = field.getAnnotation(Length.class);
if (length != null) {
chain.addLastHandler(new LengthValidatorHandler(length.value()));
}
return chain;
}
然后我们构建好链之后再执行chain.validate(field.get(bean));跳转到ValidatorChain中的validate方法,由于name字段的责任链只有一个校验器,所以如何有异常就会将错误的信息抛出,这样就是一个字段校验的完整流程,对age的校验也遵循这个流程。
public void validate(Object value) throws ValidatorException {
ValidatorContext context = new ValidatorContext(value);
while (true) {
int index = context.currentIndex();
if (index == handlers.size()) {
break;
}
ValidatorHandler handler = handlers.get(index);
handler.validate(context.getValue(), context);
if (index == context.currentIndex()) {
break;
}
}
context.throwExceptionIfNecessary();
}
如果后续有什么其他的需要,那么基本都可以通过增加ValidatorContext中的字段来实现,例如,年龄的责任链是两个,那么应当显示有两个错误,所以我们可以再ValidatorContext添加一个errorMessageList,来实现将这两个错误都展示出来,而不是只能输出一个错误。
总结一下,责任链模式像一场精心设计的接力赛——每个选手专注自己的赛道,棒子(请求)传到谁手里,谁就决定冲刺还是交棒,最终高效到达终点