当前位置: 首页 > news >正文

小架构step系列28:自定义校验注解

1 概述

hibernate-validator提供了很多内置的校验注解(如@NotNull),还提供了可自定义校验注解的机制,这个对业务系统会比较方便。把一些通用的校验变成校验注解,方便各个业务使用,正是框架应该做的事情,本文了解一下自定义校验注解的机制。

2 原理

2.1 例子

先看一个例子,再根据例子来看原理。业务系统不少地方需要用到手机号,中国手机号有点特殊,它由国家码和11位的手机号码组成,要校验的话就得校验这两个信息都得合法。如果让每个业务都写这种校验,也是比较麻烦的,这个时候就可以用自定义校验注解的方式来提供便利。

1) 定义手机号类。

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Value;@Schema(description = "手机号")
@Value
@AllArgsConstructor
public class MobilePhone {@Schema(description = "国家码", requiredMode = Schema.RequiredMode.REQUIRED)String countryCode;@Schema(description = "手机号码", requiredMode = Schema.RequiredMode.REQUIRED)String phoneCode;
}

2) 定义一个注解@MobilePhoneValid,注解里面带@Constraint标识。

@Target({ METHOD, FIELD })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
public @interface MobilePhoneValid {String message() default "{mobile.phone.message}";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };
}

3) 定义一个ConstraintValidator

// ConstraintValidator泛型的第一个参数为注解,第二个参数为要校验的对象类型
public class MobilePhoneConstraintValidator implements ConstraintValidator<MobilePhoneValid, MobilePhone> {private Pattern pattern = Pattern.compile("^1[3-9]\\d{9}$");@Overridepublic boolean isValid(MobilePhone value, ConstraintValidatorContext context) {if(value != null) {return value.getCountryCode() != null && pattern.matcher(value.getPhoneCode()).matches();}return true;}
}

4) 把校验注解和ConstraintValidator关联起来

// 在@Constraint的validatedBy属性指定ConstraintValidator类。
@Constraint(validatedBy = {MobilePhoneConstraintValidator.class})
public @interface MobilePhoneValid {}

5) 在属性中使用校验注解

public class GroupMember {@Schema(description = "手机号码", implementation = GroupMemberGender.class)@MobilePhoneValidprivate MobilePhone mobilePhone;
}

自定义好注解和对应的ConstraintValidator之后,就普通的校验注解没有什么区别,说明这个机制设计得比较好。

2.2 查找自定义的ConstraintValidator

在查找请求参数对象哪里配置了校验相关的注解的时候,会把有校验相关的注解标记的字段/方法/类的信息封装到ConstraintDescriptorImpl中。比如用@NotNull注解标注了GroupMember对象的name属性时,ConstraintDescriptorImpl就包装了GroupMember对象的name属性的相关信息。

// 源码位置:org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl
public ConstraintDescriptorImpl(ConstraintHelper constraintHelper,// 如果是参数对象的属性,则constrainable是一个JavaBeanField,即封装了这个对象属性信息Constrainable constrainable, // 校验相关的注解信息,如@NotNull注解ConstraintAnnotationDescriptor<T> annotationDescriptor,// 指明注解标注的类型,如果是对象的属性,constraintLocationKind为FIELDConstraintLocationKind constraintLocationKind,Class<?> implicitGroup,ConstraintOrigin definedOn,ConstraintType externalConstraintType) {this.annotationDescriptor = annotationDescriptor;this.constraintLocationKind = constraintLocationKind;this.definedOn = definedOn;this.isReportAsSingleInvalidConstraint = annotationDescriptor.getType().isAnnotationPresent(ReportAsSingleViolation.class);this.groups = buildGroupSet( annotationDescriptor, implicitGroup );this.payloads = buildPayloadSet( annotationDescriptor );this.valueUnwrapping = determineValueUnwrapping( this.payloads, constrainable, annotationDescriptor.getType() );this.validationAppliesTo = determineValidationAppliesTo( annotationDescriptor );this.constraintValidatorClasses = constraintHelper.getAllValidatorDescriptors( annotationDescriptor.getType() ).stream().map( ConstraintValidatorDescriptor::getValidatorClass ).collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );// 1. 使用constraintHelper找注解对应的ConstraintValidator,constraintHelper为org.hibernate.validator.internal.metadata.core.ConstraintHelperList<ConstraintValidatorDescriptor<T>> crossParameterValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(annotationDescriptor.getType(),ValidationTarget.PARAMETERS) );List<ConstraintValidatorDescriptor<T>> genericValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(annotationDescriptor.getType(),ValidationTarget.ANNOTATED_ELEMENT) );if ( crossParameterValidatorDescriptors.size() > 1 ) {throw LOG.getMultipleCrossParameterValidatorClassesException( annotationDescriptor.getType() );}this.constraintType = determineConstraintType(annotationDescriptor.getType(),constrainable,!genericValidatorDescriptors.isEmpty(),!crossParameterValidatorDescriptors.isEmpty(),externalConstraintType);this.composingConstraints = parseComposingConstraints( constraintHelper, constrainable, constraintType );this.compositionType = parseCompositionType( constraintHelper );validateComposingConstraintTypes();if ( constraintType == ConstraintType.GENERIC ) {this.matchingConstraintValidatorDescriptors = CollectionHelper.toImmutableList( genericValidatorDescriptors );}else {this.matchingConstraintValidatorDescriptors = CollectionHelper.toImmutableList( crossParameterValidatorDescriptors );}this.hashCode = annotationDescriptor.hashCode();
}// 源码位置:org.hibernate.validator.internal.metadata.core.ConstraintHelper
public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> findValidatorDescriptors(Class<A> annotationType, ValidationTarget validationTarget) {// 2. 获取所有符合条件的ConstraintValidatorreturn getAllValidatorDescriptors( annotationType ).stream().filter( d -> supportsValidationTarget( d, validationTarget ) ).collect( Collectors.toList() );
}
public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getAllValidatorDescriptors(Class<A> annotationType) {Contracts.assertNotNull( annotationType, MESSAGES.classCannotBeNull() );// 3. 根据注解类型获取默认的ConstraintValidatorreturn validatorDescriptors.computeIfAbsent( annotationType, a -> getDefaultValidatorDescriptors( a ) );
}// 源码位置:org.hibernate.validator.internal.metadata.core.ConstraintHelper
private <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getDefaultValidatorDescriptors(Class<A> annotationType) {// 4. 根据注解类型从内置的列表里先获取ConstraintValidator//    ConstraintHelper在初始化的时候,就已经根据内置的注解类型硬编码了enabledBuiltinConstraints,把注解类型和对应的ConstraintValidator映射上了//    如果是自定义的注解类型,则不在enabledBuiltinConstraints里面final List<ConstraintValidatorDescriptor<A>> builtInValidators = (List<ConstraintValidatorDescriptor<A>>) enabledBuiltinConstraints.get( annotationType );if ( builtInValidators != null ) {return builtInValidators;}// 5. 自定义类型的注解,从注解里获取@Constraint注解指定的,从@Constraint注解指定的validatedBy={}中取对应的ConstraintValidator(取到的是Class数组)Class<? extends ConstraintValidator<A, ?>>[] validatedBy = (Class<? extends ConstraintValidator<A, ?>>[]) annotationType.getAnnotation( Constraint.class ).validatedBy();// 6. 把ConstraintValidator包装到ClassBasedValidatorDescriptorreturn Stream.of( validatedBy ).map( c -> ConstraintValidatorDescriptor.forClass( c, annotationType ) ).collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
}// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorDescriptor
static <A extends Annotation> ConstraintValidatorDescriptor<A> forClass(Class<? extends ConstraintValidator<A, ?>> validatorClass, Class<? extends Annotation> constraintAnnotationType) {// 7. 包装return ClassBasedValidatorDescriptor.of( validatorClass, constraintAnnotationType );
}public static <T extends Annotation> ClassBasedValidatorDescriptor<T> of(Class<? extends ConstraintValidator<T, ?>> validatorClass, Class<? extends Annotation> registeredConstraintAnnotationType) {Type definedConstraintAnnotationType = TypeHelper.extractConstraintType( validatorClass );if ( !registeredConstraintAnnotationType.equals( definedConstraintAnnotationType ) ) {throw LOG.getConstraintValidatorDefinitionConstraintMismatchException( validatorClass, registeredConstraintAnnotationType,definedConstraintAnnotationType );}// 8. 包装到ClassBasedValidatorDescriptor,ConstraintValidator为它的validatorClassreturn new ClassBasedValidatorDescriptor<T>( validatorClass );
}// 回到ConstraintDescriptorImpl的构造方法继续处理
// 源码位置:org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl
public ConstraintDescriptorImpl(ConstraintHelper constraintHelper,// 如果是参数对象的属性,则constrainable是一个JavaBeanField,即封装了这个对象属性信息Constrainable constrainable, // 校验相关的注解信息,如@NotNull注解ConstraintAnnotationDescriptor<T> annotationDescriptor,// 指明注解标注的类型,如果是对象的属性,constraintLocationKind为FIELDConstraintLocationKind constraintLocationKind,Class<?> implicitGroup,ConstraintOrigin definedOn,ConstraintType externalConstraintType) {this.annotationDescriptor = annotationDescriptor;this.constraintLocationKind = constraintLocationKind;this.definedOn = definedOn;this.isReportAsSingleInvalidConstraint = annotationDescriptor.getType().isAnnotationPresent(ReportAsSingleViolation.class);this.groups = buildGroupSet( annotationDescriptor, implicitGroup );this.payloads = buildPayloadSet( annotationDescriptor );this.valueUnwrapping = determineValueUnwrapping( this.payloads, constrainable, annotationDescriptor.getType() );this.validationAppliesTo = determineValidationAppliesTo( annotationDescriptor );this.constraintValidatorClasses = constraintHelper.getAllValidatorDescriptors( annotationDescriptor.getType() ).stream().map( ConstraintValidatorDescriptor::getValidatorClass ).collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );// 1. 使用constraintHelper找注解对应的ConstraintValidator,constraintHelper为org.hibernate.validator.internal.metadata.core.ConstraintHelperList<ConstraintValidatorDescriptor<T>> crossParameterValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(annotationDescriptor.getType(),ValidationTarget.PARAMETERS) );List<ConstraintValidatorDescriptor<T>> genericValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(annotationDescriptor.getType(),ValidationTarget.ANNOTATED_ELEMENT) );if ( crossParameterValidatorDescriptors.size() > 1 ) {throw LOG.getMultipleCrossParameterValidatorClassesException( annotationDescriptor.getType() );}this.constraintType = determineConstraintType(annotationDescriptor.getType(),constrainable,!genericValidatorDescriptors.isEmpty(),!crossParameterValidatorDescriptors.isEmpty(),externalConstraintType);this.composingConstraints = parseComposingConstraints( constraintHelper, constrainable, constraintType );this.compositionType = parseCompositionType( constraintHelper );validateComposingConstraintTypes();// 9. 找到的ConstraintValidator列表赋值给matchingConstraintValidatorDescriptorsif ( constraintType == ConstraintType.GENERIC ) {this.matchingConstraintValidatorDescriptors = CollectionHelper.toImmutableList( genericValidatorDescriptors );}else {this.matchingConstraintValidatorDescriptors = CollectionHelper.toImmutableList( crossParameterValidatorDescriptors );}this.hashCode = annotationDescriptor.hashCode();
}
从上面流程来看,创建有校验注解的类/属性/方法对应的ConstraintDescriptorImpl时候,自定义的校验注解和hibernate-validator包内置的注解有两个地方不同:
  • 注解需要有@Constraint注解标识,有@Constraint的自定义注解才认为是校验相关的注解。
  • 在@Constraint注解的validatedBy参数指定对应的ConstraintValidator,可以指多个。不指定就无法校验。

2.3 使用自定义的ConstraintValidator

hibernate-validator包的校验由org.hibernate.validator.internal.engine.ValidatorImpl提供,最终交给ConstraintTree来协调具体的ConstraintValidator来完成校验。
// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree
public final boolean validateConstraints(ValidationContext<?> validationContext, ValueContext<?, ?> valueContext) {List<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts = new ArrayList<>( 5 );// 1. 校验,SimpleConstraintTree继承于ConstraintTree,SimpleConstraintTree重载了validateConstraints()方法validateConstraints( validationContext, valueContext, violatedConstraintValidatorContexts );if ( !violatedConstraintValidatorContexts.isEmpty() ) {for ( ConstraintValidatorContextImpl constraintValidatorContext : violatedConstraintValidatorContexts ) {for ( ConstraintViolationCreationContext constraintViolationCreationContext : constraintValidatorContext.getConstraintViolationCreationContexts() ) {validationContext.addConstraintFailure(valueContext, constraintViolationCreationContext, constraintValidatorContext.getConstraintDescriptor());}}return false;}return true;
}// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree
protected void validateConstraints(ValidationContext<?> validationContext, ValueContext<?, ?> valueContext, Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {// 2. 初始化ConstraintValidator,getInitializedConstraintValidator()由父类ConstraintTree实现ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );// create a constraint validator contextConstraintValidatorContextImpl constraintValidatorContext = validationContext.createConstraintValidatorContextFor(descriptor, valueContext.getPropertyPath());// validateif ( validateSingleConstraint( valueContext, constraintValidatorContext, validator ).isPresent() ) {violatedConstraintValidatorContexts.add( constraintValidatorContext );}
}// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree
protected final ConstraintValidator<A, ?> getInitializedConstraintValidator(ValidationContext<?> validationContext, ValueContext<?, ?> valueContext) {ConstraintValidator<A, ?> validator;// 如果已经初始化过,则直接使用if ( validationContext.getConstraintValidatorManager().isPredefinedScope() ) {validator = defaultInitializedConstraintValidator;}else {if ( validationContext.getConstraintValidatorFactory() == validationContext.getConstraintValidatorManager().getDefaultConstraintValidatorFactory()&& validationContext.getConstraintValidatorInitializationContext() == validationContext.getConstraintValidatorManager().getDefaultConstraintValidatorInitializationContext() ) {validator = defaultInitializedConstraintValidator;if ( validator == null ) {synchronized ( this ) {validator = defaultInitializedConstraintValidator;if ( validator == null ) {// 3. 第一次获取ConstraintValidator需要初始化,ConstraintValidatorManager为ConstraintValidatorManagerImplvalidator = validationContext.getConstraintValidatorManager().getInitializedValidator(validatedValueType,descriptor,validationContext.getConstraintValidatorManager().getDefaultConstraintValidatorFactory(),validationContext.getConstraintValidatorManager().getDefaultConstraintValidatorInitializationContext() );defaultInitializedConstraintValidator = validator;}}}}else {validator = validationContext.getConstraintValidatorManager().getInitializedValidator(validatedValueType,descriptor,validationContext.getConstraintValidatorFactory(),validationContext.getConstraintValidatorInitializationContext());}}if ( validator == null ) {throw getExceptionForNullValidator( validatedValueType, valueContext.getPropertyPath().asString() );}return validator;
}// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl
public <A extends Annotation> ConstraintValidator<A, ?> getInitializedValidator(Type validatedValueType,ConstraintDescriptorImpl<A> descriptor,ConstraintValidatorFactory constraintValidatorFactory,HibernateConstraintValidatorInitializationContext initializationContext) {Contracts.assertNotNull( validatedValueType );Contracts.assertNotNull( descriptor );Contracts.assertNotNull( constraintValidatorFactory );Contracts.assertNotNull( initializationContext );CacheKey key = new CacheKey( descriptor.getAnnotationDescriptor(), validatedValueType, constraintValidatorFactory, initializationContext );@SuppressWarnings("unchecked")ConstraintValidator<A, ?> constraintValidator = (ConstraintValidator<A, ?>) constraintValidatorCache.get( key );if ( constraintValidator == null ) {// 4. 创建并初始化ConstraintValidator,ConstraintValidatorManagerImpl继承AbstractConstraintValidatorManagerImpl,//    createAndInitializeValidator()由父类实现constraintValidator = createAndInitializeValidator( validatedValueType, descriptor, constraintValidatorFactory, initializationContext );constraintValidator = cacheValidator( key, constraintValidator );}else {LOG.tracef( "Constraint validator %s found in cache.", constraintValidator );}return DUMMY_CONSTRAINT_VALIDATOR == constraintValidator ? null : constraintValidator;
}// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.AbstractConstraintValidatorManagerImpl
protected <A extends Annotation> ConstraintValidator<A, ?> createAndInitializeValidator(Type validatedValueType,ConstraintDescriptorImpl<A> descriptor,ConstraintValidatorFactory constraintValidatorFactory,HibernateConstraintValidatorInitializationContext initializationContext) {// 找到包装ConstraintValidator的ConstraintValidatorDescriptor,自定义的一般包装到ClassBasedValidatorDescriptorConstraintValidatorDescriptor<A> validatorDescriptor = findMatchingValidatorDescriptor( descriptor, validatedValueType );ConstraintValidator<A, ?> constraintValidator;if ( validatorDescriptor == null ) {return null;}// 5. 用工厂去创建ConstraintValidatorconstraintValidator = validatorDescriptor.newInstance( constraintValidatorFactory );initializeValidator( descriptor, constraintValidator, initializationContext );return constraintValidator;
}// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ClassBasedValidatorDescriptor
public ConstraintValidator<A, ?> newInstance(ConstraintValidatorFactory constraintValidatorFactory) {// 6. 创建ConstraintValidator实例ConstraintValidator<A, ?> constraintValidator = constraintValidatorFactory.getInstance( validatorClass );if ( constraintValidator == null ) {throw LOG.getConstraintValidatorFactoryMustNotReturnNullException( validatorClass );}return constraintValidator;
}// 回到AbstractConstraintValidatorManagerImpl的createAndInitializeValidator()继续处理
// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.AbstractConstraintValidatorManagerImpl
protected <A extends Annotation> ConstraintValidator<A, ?> createAndInitializeValidator(Type validatedValueType,ConstraintDescriptorImpl<A> descriptor,ConstraintValidatorFactory constraintValidatorFactory,HibernateConstraintValidatorInitializationContext initializationContext) {// 找到包装ConstraintValidator的ConstraintValidatorDescriptor,自定义的一般包装到ClassBasedValidatorDescriptorConstraintValidatorDescriptor<A> validatorDescriptor = findMatchingValidatorDescriptor( descriptor, validatedValueType );ConstraintValidator<A, ?> constraintValidator;if ( validatorDescriptor == null ) {return null;}// 5. 用工厂去创建ConstraintValidatorconstraintValidator = validatorDescriptor.newInstance( constraintValidatorFactory );// 7. 初始化ConstraintValidatorinitializeValidator( descriptor, constraintValidator, initializationContext );return constraintValidator;
}// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.AbstractConstraintValidatorManagerImpl
private <A extends Annotation> void initializeValidator(ConstraintDescriptor<A> descriptor,ConstraintValidator<A, ?> constraintValidator,HibernateConstraintValidatorInitializationContext initializationContext) {try {if ( constraintValidator instanceof HibernateConstraintValidator ) {( (HibernateConstraintValidator<A, ?>) constraintValidator ).initialize( descriptor, initializationContext );}// 8. initialize()是ConstraintValidator的两个接口之一,调initialize()初始化,参数是标识要校验的注解constraintValidator.initialize( descriptor.getAnnotation() );}catch (RuntimeException e) {if ( e instanceof ConstraintDeclarationException ) {throw e;}throw LOG.getUnableToInitializeConstraintValidatorException( constraintValidator.getClass(), e );}
}
// 源码位置:javax.validation.ConstraintValidator
public interface ConstraintValidator<A extends Annotation, T> {default void initialize(A constraintAnnotation) {}boolean isValid(T value, ConstraintValidatorContext context);
}// 回到SimpleConstraintTree的validateConstraints()进行校验
// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree
protected void validateConstraints(ValidationContext<?> validationContext, ValueContext<?, ?> valueContext, Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {// 2. 初始化ConstraintValidator,getInitializedConstraintValidator()由父类ConstraintTree实现ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );ConstraintValidatorContextImpl constraintValidatorContext = validationContext.createConstraintValidatorContextFor(descriptor, valueContext.getPropertyPath());// 9. 进行校验,validateSingleConstraint()由父类ConstraintTree实现if ( validateSingleConstraint( valueContext, constraintValidatorContext, validator ).isPresent() ) {violatedConstraintValidatorContexts.add( constraintValidatorContext );}
}// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree
protected final <V> Optional<ConstraintValidatorContextImpl> validateSingleConstraint(ValueContext<?, ?> valueContext,ConstraintValidatorContextImpl constraintValidatorContext,ConstraintValidator<A, V> validator) {boolean isValid;try {// 10. 获取标记了校验注解的属性值V validatedValue = (V) valueContext.getCurrentValidatedValue();// 11. 调ConstraintValidator的isValid()接口进行校验isValid = validator.isValid( validatedValue, constraintValidatorContext );}catch (RuntimeException e) {if ( e instanceof ConstraintDeclarationException ) {throw e;}throw LOG.getExceptionDuringIsValidCallException( e );}if ( !isValid ) {return Optional.of( constraintValidatorContext );}return Optional.empty();
}

从上面校验流程来看,自定义的ConstraintValidator和hibernate-validator包内置的ConstraintValidator并没有什么不同,都是在查找的过程中把对应的ConstraintValidator类找到,然后把Class包装到ValidatorDescriptor。在校验的时候,从ValidatorDescriptor中取出ConstraintValidator类,创建实例,调用实例的initialize()方法进行实例化,最后调实例的isValid()方法进行实际的校验。

3 架构一小步

自定义需要的校验注解和对应的ConstraintValidator,提供更丰富的校验能力。

http://www.dtcms.com/a/303590.html

相关文章:

  • 【算法训练营Day17】二叉树part7
  • 【VASP】二维材料杨氏模量与泊松比的公式
  • OpenLayers 综合案例-信息窗体-弹窗
  • 打卡day5
  • C++面试5题--5day
  • C++中的“对象切片“:一场被截断的继承之痛
  • 【SpringMVC】MVC中Controller的配置 、RestFul的使用、页面重定向和转发
  • rhel9.1配置本地源并设置开机自动挂载(适用于物理光驱的场景)
  • c++ 基础
  • windows内核研究(异常-CPU异常记录)
  • 嵌入式分享合集186
  • STM32时钟源
  • JavaScript手录09-内置对象【String对象】
  • 第一章:Go语言基础入门之函数
  • wrk 压力测试工具使用教程
  • 屏幕晃动机cad【4张】三维图+设计说明书
  • 多信号实采数据加噪版本
  • 详解 Electron 应用增量升级
  • 轻量级远程开发利器:Code Server与cpolar协同实现安全云端编码
  • 2. 编程语言-JAVA-Spring Security
  • 记录自己第n次面试(n>3)
  • JavaScript手录08-对象
  • 深入解析IPMI FRU规范:分区结构与字段标识详解
  • 10_opencv_分离颜色通道、多通道图像混合
  • Nuxt3 全栈作品【通用信息管理系统】修改密码
  • OpenLayers 综合案例-热力图
  • 在虚拟机ubuntu上修改framebuffer桌面不能显示图像
  • C++进阶—C++11
  • 5G 便携式多卡图传终端:移动作业的 “实时感知纽带”
  • 【unitrix】 6.19 Ord特质(ord.rs)