当前位置: 首页 > 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.lryc.cn/news/602139.html

相关文章:

  • “太赫兹”
  • KubeSphere理论及实战
  • Error reading config file (/home/ansible.cfg): ‘ACTION_WARNINGS(default) = True
  • 什么是3DVR?VR技术有哪些应用场景?
  • 关于sql面试积累
  • 红绿灯纵向距离的评估
  • 【查漏补缺】机器学习典型算法
  • 【Java Web实战】从零到一打造企业级网上购书网站系统 | 完整开发实录(终)
  • 应用加速游戏盾的安全作用
  • Java BigDecimal详解:小数精确计算、使用方法与常见问题解决方案
  • 【数据库】使用Sql Server将分组后指定字段的行数据转为一个字段显示,并且以逗号隔开每个值,收藏不迷路
  • GaussDB 开发基本规范
  • 22 BTLO 蓝队靶场 Countdown 解题记录
  • 如何利用机器学习分析筛选生物标记物
  • 微信小程序——早餐小程序
  • TMS320F28335PGFA TI德州仪器:32位浮点内核+CLA协处理器DSP,工业控制性能极限!
  • 【Linux指南】Linux粘滞位详解:解决共享目录文件删除安全隐患
  • CJ02、CJ20N下达项目报错用户状态 初始 是活动的,怎么解决?
  • 模型压缩的一些整理
  • 异步通讯组件MQ
  • 【Linux系统】Ext2文件系统 | 软硬链接
  • 医疗人工智能高质量数据集和语料库建设路径探析
  • HOT100——链表篇Leetcode206. 反转链表
  • qt 心跳包
  • Java面试宝典:Spring Boot
  • 解决MySQL 1055错误:ONLY_FULL_GROUP_BY问题详解(MySQL 8.0版)
  • Java项目接口权限校验的灵活实现
  • Datawhale AI夏令营 task2 笔记问题汇总收集
  • Python 实现服务器自动故障处理工具:从监控到自愈的完整方案
  • PCS液相色谱柱:专为碱性化合物设计的高性能色谱柱