В начале эта серия представляет собой краткий обзор развития последних нескольких лет. Еще во время стажировки я услышал, как тогдашний инструктор сказал: «Код должен быть написан элегантно, чтобы отражать уровень». Эта фраза стала кредо моей кодерской карьеры. При разработке старайтесь, чтобы код был как можно более кратким и элегантным.
Во многих проектах можно встретить в предшественниках оставленные позади «реликвии», присмотритесь внимательно, похожие на некоторые из следующих проверяйте код по всему интерфейсному слою, поэтому я стараюсь оптимизировать этот повторяющийся, но необходимый код. Tinker разбил поиск на проверку фреймворка, проверку с использованием аннотаций, значительно сокращающую количество избыточного кода, вы также можете использовать контрольную сумму четности для разных групп разных сцен с объектным объектом, и разработчик также может самостоятельно расширять тип аннотации проверки может поддержать. После двух лет практики я думаю, что это очень ароматно!
@RestController
@RequestMapping("/xxx")
public class XXController {
@Autowired
privete xxxServer xxService;
@PostMapping("/aaa")
public String saveUser(@RequestParam("name") String name,
@RequestParam("age") Integer age,
@RequestParam("desc") String description) {
//点个赞,业务逻辑就应该严格控制入参的合法性
if (name.length() < 1 || name.length() > 20) {
throw new IllegalArgumentException("名字长度在1-20");
}
if (age < 0) {
throw new IllegalArgumentException("年龄不能小于0");
}
return "success";
@PostMapping("/aaa")
public String saveUser(@Validated UserDto userDto) {
return "success";
}
}
public class UserDto {
@NotNull(message = "名不能为空")
@Length(min = 1, max = 20, message = "名字长度1-20")
private String name;
@Size(min = 0, message = "年龄不能小于0")
private Integer age;
}
1. Внедрение рамок и принцип реализации
Во-первых, давайте разберемся в происхождении структуры проверки, которая возникла из предложения JSR303, и сначала объясним, что такое JSR.
JSR — это аббревиатура от Java Specification Requests, что означает предложение спецификации Java. указывает наJCP(Процесс сообщества Java) сделал официальный запрос на добавление стандартизированной технической спецификации. Любой может отправить JSR для добавления новых API и сервисов на платформу Java. JSR стал важным стандартом в мире Java. (аналогично мерж-реквесту, который мы делали при использовании git)
Среди них JSR-303 — это подстандарт в JAVA EE 6, называемый Bean Validation, который определяет соответствующую модель метаданных и API для проверки JavaBean. В приложении вы можете убедиться в правильности модели данных (JavaBean) с помощью Bean Validation или собственных определенных ограничений, таких как @NotNull, @Max, @ZipCode и других аннотаций. Ограничение может быть прикреплено к полю, методу получения, классу или интерфейсу. Для некоторых конкретных требований пользователи могут легко разработать собственные ограничения. Вся структура валидации в основном состоит из валидатора (валидатор) + ValidatorContext (контекст валидатора) + ValidatorFactory (фабрика валидатора) _+ Validation (тело валидатора), это определения интерфейса, все реализованные реализации валидации Все основаны на этих интерфейсах.
Ниже приведена простая демонстрация с использованием hibernate-validator.
//1.生成校验器工厂对象
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
//2.生成校验器
Validator validator = factory.getValidator();
//3.进行校验
Set> set = validator.validate(employee);
for (ConstraintViolation constraintViolation : set) {
System.out.println(constraintViolation.getMessage());
}
Ниже приведен ссылка на основной способ вызова метода. Основная проблема заключается в том, как определяется валидатор и процесс проверки. Есть много комментариев в Интернете, которые используются эти аннотации, но нет реального упоминания о том, как эта аннотация связана с Процесс валидации., который использует абстрактный заводской шаблон для генерации валидаторов.
|-> validatorImpl.validate
|-> validatorImpl.validateInContext
|-> validatorImpl.validateConstraintsForCurrentGroup
|-> validateImpl.validateConstraintsForDefaultGroup
|-> validateImpl.validateConstraintsForSingleDefaultGroupElement
|-> validateImpl.validateMetaConstraint
|-> MetaConstraint.validateConstraint
|-> MetaConstraint.doValidateConstraint
|-> ConstraintTree.validateConstraints
|-> SimpleConstraintTree.validateConstraints
|-> ConstraintTree.validateSingleConstraint
|-> NotNullValidator.isValid
//校验注解定义
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class[] groups() default { };
Class[] payload() default { };
}
//非空校验器
public class NullValidator implements ConstraintValidator {
@Override
public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
return object == null;
}
}
protected final ConstraintValidator
getInitializedConstraintValidator(ValidationContext validationContext, ValueContext valueContext) {
ConstraintValidator validator;
......
validator = validationContext.getConstraintValidatorManager().getInitializedValidator(
validatedValueType,
descriptor,
validationContext.getConstraintValidatorFactory(),
validationContext.getConstraintValidatorInitializationContext()
return validator;
}
public ConstraintValidator getInitializedValidator(
Type validatedValueType,
ConstraintDescriptorImpl descriptor,
ConstraintValidatorFactory constraintValidatorFactory,
HibernateConstraintValidatorInitializationContext initializationContext) {
CacheKey key = new CacheKey( descriptor.getAnnotationDescriptor(),
validatedValueType, constraintValidatorFactory, initializationContext );
ConstraintValidator constraintValidator =
(ConstraintValidator) constraintValidatorCache.get( key );
//通过不同的注解类型生成并缓存校验器
if ( constraintValidator == null ) {
constraintValidator = createAndInitializeValidator( validatedValueType, descriptor, constraintValidatorFactory, initializationContext );
constraintValidator = cacheValidator( key, constraintValidator );
}
return DUMMY_CONSTRAINT_VALIDATOR == constraintValidator ? null : constraintValidator;
}
2. Применение весной
2.1 Инициализация проверки
В примере в начале мы видели, что spring также интегрирует фреймворк проверки, как это реализовано? Во-первых, spring-context имеет расширенную реализацию фреймворка валидатора, которая дает валидатору больше возможностей через smartValidator/SpringValidatorAdapter, и выполняет метод обработки после инициализации свойств бина после метода LocalValidatorFactoryBean#afterPropertiesSet. одновременно Инициализируется производственный объект валидатора.
public class LocalValidatorFactoryBean extends SpringValidatorAdapter
implements ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean {
public void afterPropertiesSet() {
......
//这里加载了hibernate-5.2版本的classloader
if (this.applicationContext != null) {
try {
Method eclMethod = configuration.getClass().getMethod("externalClassLoader", ClassLoader.class);
ReflectionUtils.invokeMethod(eclMethod, configuration, this.applicationContext.getClassLoader());
}
catch (NoSuchMethodException ex) {
}
}
//初始化了校验器生成工厂类对象
ConstraintValidatorFactory targetConstraintValidatorFactory = this.constraintValidatorFactory;
....
}
В предыдущем примере валидатор играет роль в обработке контроллера, так где же выполняется это действие? Знакомые детские туфли фреймворка spring будут думать об этой проверке объекта при обработке параметров запроса на парсинг.Посмотрим,какую логику выполняет метод ModelAttributeMethodProcessor.Ниже представлена цепочка вызовов обработки http запросов spring.
public class ModelAttributeMethodProcessor
implements HandlerMethodArgumentResolver,
HandlerMethodReturnValueHandler {
@Override
public final Object resolveArgument(....) throws Exception {
BindingResult bindingResult = null;
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
//使用校验器对每个参数对象校验
validateIfApplicable(binder, parameter);
bindingResult = binder.getBindingResult();
}
}
public class ModelAttributeMethodProcessor
implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
for (Annotation ann : parameter.getParameterAnnotations()) {
//找出需要校验的属性
Object[] validationHints = determineValidationHints(ann);
if (validationHints != null) {
binder.validate(validationHints);
break;
}
}
}
}
public void validate(Object... validationHints) {
Object target = getTarget();
BindingResult bindingResult = getBindingResult();
//通过不同的类型的校验器进行校验
for (Validator validator : getValidators()) {
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
((SmartValidator) validator).validate(target, bindingResult, validationHints);
}
else if (validator != null) {
validator.validate(target, bindingResult);
}
}
}
Здесь я хочу представить WebDataBinder, который на самом деле является преобразователем атрибута объекта параметра параметра http-запроса -> метод обработки.Например, http-запрос содержит строку «2020-02-03 10:00:00», как преобразовать его в объект Как насчет типа LocalDateTime в свойстве? В структуре Spring есть редактор, который помогает разработчикам выполнять эту операцию преобразования. Вот также использование WebDataBind для преобразования ошибки проверки и ответа.
Можно ли использовать валидатор не только на уровне контроллера, но и в методах? Фактически, среда Spring также вводит перехватчики методов.Пока @Validatedj используется в методе, проверка метода может выполняться для параметров метода.
//方法拦截器
public class MethodValidationInterceptor implements MethodInterceptor {
...
public Object invoke(MethodInvocation invocation) throws Throwable
....
//进行参数校验
result = execVal.validateParameters(
invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
....
//执行正常逻辑
Object returnValue = invocation.proceed();
....
return returnValue;
}
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements InitializingBean {
private Class validatedAnnotationType = Validated.class;
public void afterPropertiesSet() {
//定义注解切点,只要是@Validate注解
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
//生成切点通知器
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
}
4. Собственно боевая часть
4.1 контрольный пакет
Параметры запроса инкапсулируются в объект параметра, а различные условия проверки группируются с помощью тега groups;
//定义请求体封装类
public class MomentQuery extends PageQuery {
@NotEmpty(message = "班级uid不能为空", groups = {QueryGroup.MomentClassBase.class})
@Length(min = 1, max = 32, message = "班级uid长度不能超过32",groups = {QueryGroup.MomentClassBase.class})
private String classUid;
}
//使用分组校验请求参数
@GetMapping(value = "/v1/student/{classUid}/images")
public Object listImage(@Validated(value =
{QueryGroup.MomentStudentBase.class}) MomentQuery query) {
.....
}
//封装校验结果
public static String buildErrorMessage(BindingResult result) {
StringBuilder message = new StringBuilder();
List list = result.getAllErrors();
if (!CollectionUtils.isEmpty(list)) {
Iterator var3 = list.iterator();
while(var3.hasNext()) {
ObjectError elem = (ObjectError)var3.next();
String defaultMessage = elem.getDefaultMessage();
if (StringUtils.isNotEmpty(defaultMessage)) {
message.append(defaultMessage).append(" ");
}
}
}
return message.toString().trim();
}
4.2 Пользовательские аннотации валидатора
Пользовательские аннотации на самом деле довольно просты, если они соответствуют спецификации JSR303, фреймворк автоматически поможет разработчикам загрузить валидатор Validator при сканировании структуры класса.
/**
* 性别约束校验注解
*/
@Target({ ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SexConstraintValidator.class)
public @interface Sex {
String message() default "性别有误";
Class[] groups() default { };
Class[] payload() default { };
}
/**
* 定义逻辑判断
*/
public class SexConstraintValidator implements ConstraintValidator {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && (value.equals("男") || value.equals("女"));
}
}
public class UserDto {
@NotNull(message = "名不能为空")
@Length(min = 1, max = 20, message = "名字长度在1-20")
private String name;
@Size(min = 0, message = "年龄不能小于0")
private Integer age;
@Sex(message = "性别有误")
private String sex;
}
5. Резюме
Спецификация JSR303 действительно многое сделала для сокращения избыточного кода и снижения нагрузки на кодирование, а фреймворк обладает высокой расширяемостью.Видатор применяется не только в системе Spring, но и в сценариях фреймворка RPC, таких как dubbo, который мы используем. . Наша команда улучшила различные сценарии проверки данных в системе структуры проверки и повысила осведомленность о качестве на основе норм написания кода, которые каждый должен сначала проверить, а затем приступить к работе.