001package cn.sticki.validator.spel; 002 003import cn.sticki.validator.spel.exception.SpelNotSupportedTypeException; 004import cn.sticki.validator.spel.exception.SpelValidException; 005import cn.sticki.validator.spel.parse.SpelParser; 006import cn.sticki.validator.spel.result.FieldValidResult; 007import cn.sticki.validator.spel.result.ObjectValidResult; 008import lombok.extern.slf4j.Slf4j; 009import org.jetbrains.annotations.NotNull; 010 011import java.lang.annotation.Annotation; 012import java.lang.reflect.Field; 013import java.lang.reflect.Method; 014import java.util.ArrayList; 015import java.util.HashSet; 016import java.util.List; 017import java.util.Set; 018import java.util.concurrent.ConcurrentHashMap; 019 020/** 021 * spel 相关注解的执行器,对使用了 {@link SpelConstraint} 进行标记的注解执行校验。 022 * 023 * @author 阿杆 024 * @version 1.0 025 * @see SpelConstraint 026 * @since 2024/4/29 027 */ 028@Slf4j 029public class SpelValidExecutor { 030 031 private static final String MESSAGE = "message"; 032 033 private static final String CONDITION = "condition"; 034 035 private static final String GROUP = "group"; 036 037 /** 038 * 验证对象 039 * <p> 040 * 如果对象中有任意使用了 spel 约束注解的字段,则会对该字段进行校验。 041 * 042 * @param verifiedObject 被校验的对象 043 * @param validateGroups 分组信息,只有同组的注解才会被校验 044 * @return 对象校验结果 045 */ 046 @NotNull 047 public static ObjectValidResult validateObject(@NotNull Object verifiedObject, @NotNull Set<Object> validateGroups) { 048 log.debug("Spel validate start, class [{}], groups [{}]", verifiedObject.getClass().getName(), validateGroups); 049 050 List<FieldValidResult> validationResults = new ArrayList<>(); 051 052 // 获取类的字段 todo 缓存 053 Field[] fields = verifiedObject.getClass().getDeclaredFields(); 054 for (Field field : fields) { 055 field.setAccessible(true); 056 validateField(verifiedObject, field, validateGroups, validationResults); 057 } 058 059 ObjectValidResult validResult = new ObjectValidResult(); 060 validResult.addFieldResults(validationResults); 061 062 log.debug("Spel validate over, result {}", validResult.getErrors()); 063 return validResult; 064 } 065 066 /** 067 * 验证字段 068 */ 069 private static void validateField( 070 @NotNull Object verifiedObject, 071 @NotNull Field verifiedField, 072 @NotNull Set<Object> validGroups, 073 @NotNull List<FieldValidResult> validationResults 074 ) { 075 // 获取字段上的注解 todo 缓存 076 Annotation[] annotations = verifiedField.getAnnotations(); 077 for (Annotation originalAnno : annotations) { 078 String annoName = originalAnno.annotationType().getName(); 079 if (annoName.endsWith("$List") || annoName.endsWith("Container")) { 080 // 容器注解,需要获取容器内部的注解类型,其声明类为真实的注解类 081 Class<?> clazz = originalAnno.annotationType().getDeclaringClass(); 082 //noinspection unchecked 083 Annotation[] originalAnnoArray = verifiedField.getAnnotationsByType((Class<Annotation>) clazz); 084 for (Annotation anno : originalAnnoArray) { 085 // 执行验证 086 validateFieldAnnotation(anno, verifiedObject, verifiedField, validationResults, validGroups); 087 } 088 } else { 089 // 执行验证 090 validateFieldAnnotation(originalAnno, verifiedObject, verifiedField, validationResults, validGroups); 091 } 092 } 093 } 094 095 /** 096 * 对任意的注解执行校验,当注解不是Ex约束注解时,将返回null。 097 * 098 * @param annotation 注解数组,数组内的注解必须为同一类型 099 * @param verifiedObject 被校验的对象 100 * @param verifiedField 被校验的字段,必须存在于被校验的对象中 101 * @param validationResults 校验结果列表 102 * @param validateGroups 分组信息 103 */ 104 private static void validateFieldAnnotation( 105 @NotNull Annotation annotation, 106 @NotNull Object verifiedObject, 107 @NotNull Field verifiedField, 108 @NotNull List<FieldValidResult> validationResults, 109 @NotNull Set<Object> validateGroups 110 ) { 111 Class<? extends Annotation> annoClazz = annotation.annotationType(); 112 // 验证注解的合法性 113 if (!isSpelConstraintAnnotationCached(annoClazz)) { 114 return; 115 } 116 117 log.debug("===> Find target annotation [{}], verifiedField [{}], anno param[{}]", annoClazz.getSimpleName(), verifiedField.getName(), annotation); 118 119 // 获取验证器实例 todo 缓存 120 Class<? extends SpelConstraintValidator<?>> validatorClass = annoClazz.getAnnotation(SpelConstraint.class).validatedBy(); 121 SpelConstraintValidator<? extends Annotation> validator = getValidatorInstance(validatorClass); 122 123 // 判断对象的类型是否受支持 124 Set<Class<?>> supported = validator.supportType(); 125 Class<?> verifiedObjectClass = verifiedObject.getClass(); 126 if (supported.stream().noneMatch(clazz -> clazz.isInstance(verifiedObjectClass))) { 127 log.error("===> Object type not supported, skip validate. supported types [{}]", supported); 128 throw new SpelNotSupportedTypeException(verifiedObjectClass, supported); 129 } 130 131 // 匹配分组 132 Set<Object> annoGroups = parseGroups(annotation, verifiedObject); 133 if (!annoGroups.isEmpty() && !matchGroup(validateGroups, annoGroups)) { 134 log.debug("===> Group not matched, skip validate. annotation groups [{}]", annoGroups); 135 return; 136 } 137 138 // 判断condition条件是否成立 139 String condition = getAnnotationValue(annotation, CONDITION); 140 if (!condition.isEmpty() && !SpelParser.parse(condition, verifiedObject, Boolean.class)) { 141 log.debug("===> Condition not valid, skip validate. condition [{}]", condition); 142 return; 143 } 144 145 // 执行校验 146 FieldValidResult validationResult = doValidate(validator, annotation, verifiedObject, verifiedField); 147 validationResults.add(validationResult); 148 149 log.debug("===> Validate result [{}]", validationResult.isSuccess()); 150 } 151 152 /** 153 * 执行校验 154 * 155 * @param validator 校验器 156 * @param annotation 注解 157 * @param verifiedObject 被校验的对象 158 * @param verifiedField 被校验的字段,必须存在于被校验的对象中 159 */ 160 @NotNull 161 private static <A extends Annotation> FieldValidResult doValidate( 162 @NotNull SpelConstraintValidator<?> validator, 163 @NotNull A annotation, 164 @NotNull Object verifiedObject, 165 @NotNull Field verifiedField 166 ) { 167 FieldValidResult result; 168 try { 169 // noinspection unchecked 170 result = ((SpelConstraintValidator<A>) validator).isValid(annotation, verifiedObject, verifiedField); 171 } catch (RuntimeException e) { 172 log.error("spel validate error: {} ;Located in the annotation [{}] of class [{}] field [{}]", 173 e.getMessage(), annotation.annotationType().getName(), verifiedObject.getClass().getName(), verifiedField.getName()); 174 throw e; 175 } 176 autoFillValidResult(result, annotation, verifiedField); 177 return result; 178 } 179 180 /** 181 * 约束注解缓存 182 */ 183 private static final ConcurrentHashMap<Class<? extends Annotation>, Boolean> CONSTRAINT_ANNOTATION_CACHE = new ConcurrentHashMap<>(); 184 185 /** 186 * 判断注解是否为合法的约束注解,该方法会缓存结果。 187 */ 188 private static boolean isSpelConstraintAnnotationCached(@NotNull Class<? extends Annotation> annotationType) { 189 return CONSTRAINT_ANNOTATION_CACHE.computeIfAbsent(annotationType, SpelValidExecutor::isSpelConstraintAnnotation); 190 } 191 192 /** 193 * 判断注解是否为合法的约束注解 194 */ 195 private static boolean isSpelConstraintAnnotation(@NotNull Class<? extends Annotation> annotationType) { 196 if (!annotationType.isAnnotationPresent(SpelConstraint.class)) { 197 return false; 198 } 199 200 if (GetMethod.action(annotationType, MESSAGE).run() == null) { 201 log.warn("The annotation [{}] must have a method named [message] that returns a string.", annotationType.getName()); 202 return false; 203 } 204 205 if (GetMethod.action(annotationType, CONDITION).run() == null) { 206 log.warn("The annotation [{}] must have a method named [condition] that returns a string.", annotationType.getName()); 207 return false; 208 } 209 210 if (GetMethod.action(annotationType, GROUP).run() == null) { 211 log.warn("The annotation [{}] must have a method named [group] that returns a Array<String>.", annotationType.getName()); 212 return false; 213 } 214 215 return true; 216 } 217 218 /** 219 * 判断组别是否匹配 220 */ 221 @SuppressWarnings("RedundantIfStatement") 222 private static boolean matchGroup(@NotNull Set<Object> validateGroups, @NotNull Set<Object> annoGroups) { 223 if (validateGroups.isEmpty()) { 224 return true; 225 } 226 if (validateGroups.stream().anyMatch(annoGroups::contains)) { 227 return true; 228 } 229 return false; 230 } 231 232 /** 233 * 解析组别信息 234 */ 235 @NotNull 236 private static Set<Object> parseGroups(@NotNull Annotation annotation, @NotNull Object value) { 237 String[] groups = getAnnotationValue(annotation, GROUP); 238 Set<Object> parsedGroups = new HashSet<>(); 239 for (String group : groups) { 240 parsedGroups.add(SpelParser.parse(group, value, Object.class)); 241 } 242 return parsedGroups; 243 } 244 245 /** 246 * 自动填充校验结果的错误信息 247 */ 248 private static void autoFillValidResult(@NotNull FieldValidResult result, @NotNull Annotation annotation, @NotNull Field verifiedField) { 249 if (!result.isSuccess()) { 250 // 错误信息收集 251 if (result.getMessage().isEmpty()) { 252 String message = getAnnotationValue(annotation, MESSAGE); 253 result.setMessage(message); 254 } 255 if (result.getFieldName().isEmpty()) { 256 result.setFieldName(verifiedField.getName()); 257 } 258 } 259 } 260 261 /** 262 * 校验器实例管理器,避免重复创建校验器实例。 263 */ 264 private static final ConcurrentHashMap<Class<? extends SpelConstraintValidator<?>>, SpelConstraintValidator<?>> VALIDATOR_INSTANCE_CACHE = new ConcurrentHashMap<>(); 265 266 /** 267 * 获取校验器实例,当实例不存在时会创建一个新的实例。 268 */ 269 @NotNull 270 private static <T extends SpelConstraintValidator<?>> SpelConstraintValidator<?> getValidatorInstance(@NotNull Class<T> validator) { 271 return VALIDATOR_INSTANCE_CACHE.computeIfAbsent(validator, key -> { 272 try { 273 return key.getDeclaredConstructor().newInstance(); 274 } catch (Exception e) { 275 throw new SpelValidException("Create validator [" + validator.getName() + "] instance error: " + e.getMessage(), e); 276 } 277 }); 278 } 279 280 // todo 缓存 281 private static <T> T getAnnotationValue(@NotNull Annotation annotation, @NotNull String methodName) { 282 Method method = GetMethod.action(annotation.annotationType(), methodName).run(); 283 try { 284 //noinspection unchecked 285 return (T) method.invoke(annotation); 286 } catch (Exception e) { 287 throw new SpelValidException("Get method [" + annotation.annotationType().getName() + "." + methodName + "] error: " + e.getMessage(), e); 288 } 289 } 290 291}