spring 中的注解操作

在 spring 中,对注解的操作,都位于 spring-core 模块下的 org.springframework.core.annotation 包中。通过 annotation 包中定义的相关类,完成对类型、方法、字段等元素上注解的操作。

主要类介绍

MergedAnnotations  接口,为 Spring 中公共组件模型中的组合注解而设计,重点是属性别名和 meta-annotation。通过定义一系列 static 类型的 from 方法来创建 TypeMappedAnnotations 对象,通过 of 方法来创建 MergedAnnotationsCollection 对象。

        MergedAnnotationsCollection  MergedAnnotation 集合对应的 MergedAnnotations 实现,通过 MergedAnnotations#of 调用 MergedAnnotationsCollection.of 进行创建,只在 classreading 包下进行字节码文件访问时 SimpleMethodMetadataReadingVisitor#visitEnd 和 SimpleAnnotationMetadataReadingVisitor#visitEnd 中使用。

        TypeMappedAnnotations 通过两个 static 方法 TypeMappedAnnotations#from 进行 TypeMappedAnnotations 实例对象的创建,区别在于传参不同,一个针对 AnnotatedElement,另一个针对 Annotation[];定义了一系列 get 方法,获取 TypeMappedAnnotations 中指定 annotationType 对应的 TypeMappedAnnotation,当 TypeMappedAnnotations 中 Annotation[] 不为 null,则通过指定的 AnnotationProcessor 通过遍历 Annotation[],对指定的 Annotation,创建 AnnotationTypeMappings 映射,接着进行匹配,匹配到了,创建 TypeMappedAnnotation 对象返回,当 Annotation[] 为 null 时,委托给 AnnotationsScanner#scan 进行处理,先获取 AnnotatedElement 上持有的注解,接着就按 Annotation[] 不为 null 时进行处理。

MergedAnnotation  接口

        AbstractMergedAnnotation  抽象实现

                TypeMappedAnnotation  MergedAnnotation 主要实现类,持有 AnnotationTypeMapping。

                MissingMergedAnnotation  从 MergedAnnotations 中获取指定 annotationType 对应的MergedAnnotation 时,不存在,返回 MissingMergedAnnotation.getInstance()。

AnnotationTypeMappings  针对给定的 annotationType,构造 AnnotationTypeMapping 映射集合,会合并 meta-annotation,后续判断是否持有某个注解,也是通过对 AnnotationTypeMappings 的遍历来进行匹配。

AnnotationTypeMapping  一个 annotationType 对应一个 AnnotationTypeMapping。

AttributeMethods  对注解中定义属性方法的封装,通过 isAttributeMethod 来判断注解中某个方法是否是属性方法,即参数个数为0,返回类型不为 void。

@AliasFor 别名方法,只定义在方法上。例如定义在 @Service value 方法上,表示其是 @Component 中 value 方法的别称。

AnnotationScanner 注解扫描器,工具类,在给定 AnnotatedElement 注解的层次结构中搜索相关注解。

AnnotationUtils  定义了一系列 getAnnotation/findAnnotation 方法,通过 MergedAnnotations.from 创建 TypeMappedAnnotations 对象,接着调用 TypeMappedAnnotations#get 方法获取指定类型的 TypeMappedAnnotation,最后调用 AbstractMergedAnnotation#synthesize 创建 Annotation 对象。

AnnotatedElementUtils  工具类,为 spring 中 meta-annotation 编程模型定义的公共 API。常用 hasAnnotation 方法判断给定 AnnotatedElement 上是否持有某个类型的注解。

AnnotationFilter  注解过滤器,常用 PackagesAnnotationFilter,即根据指定的包过滤注解。

RepeatableContainers  注解容器,两个子类,RepeatableContainers$StandardRepeatableContainers,RepeatableContainers$NoRepeatableContainers,前者相较于后者增加了缓存的功能。

AnnotationsProcessor  处理注解的回调接口,常用实现类 TypeMappedAnnotations$MergedAnnotationFinder。

MergedAnnotationSelectors  选择 TypeMappedAnnotation 实例对象的策略接口,常用实现类 MergedAnnotationSelectors$Nearest。

下面通过一个应用,来看下 spring 中注解操作的逻辑实现。

包扫描模式下注解的应用

在基于注解的包扫描模式下,需要判断指定包下 class 文件是否持有指定的注解,此时通过 ClassReader 来读取字节码文件中类的相关信息,当类上持有注解时,通过 classVisitor.visitAnnotation 得到 MergedAnnotationReadingVisitor 对象来进行注解的访问,访问结束时,调用 annotationVisitor.visitEnd()。

// MergedAnnotationReadingVisitor
@Override
public void visitEnd() {MergedAnnotation<A> annotation = MergedAnnotation.of(this.classLoader, this.source, this.annotationType, this.attributes);this.consumer.accept(annotation);
}
static <A extends Annotation> MergedAnnotation<A> of(@Nullable ClassLoader classLoader, @Nullable Object source,Class<A> annotationType, @Nullable Map<String, ?> attributes) {return TypeMappedAnnotation.of(classLoader, source, annotationType, attributes);
}static <A extends Annotation> MergedAnnotation<A> of(@Nullable ClassLoader classLoader, @Nullable Object source,Class<A> annotationType, @Nullable Map<String, ?> attributes) {Assert.notNull(annotationType, "Annotation type must not be null");AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotationType);// 取 mappings 中第一个作为 TypeMappedAnnotation 中的 AnnotationTypeMappingreturn new TypeMappedAnnotation<>(mappings.get(0), classLoader, source, attributes, TypeMappedAnnotation::extractFromMap, 0);
}

首先,为指定的 annotationType 生成 AnnotationTypeMappings。

AnnotationTypeMappings 的创建

static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType) {return forAnnotationType(annotationType, new HashSet<>());
}
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,Set<Class<? extends Annotation>> visitedAnnotationTypes) {return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(),AnnotationFilter.PLAIN, visitedAnnotationTypes);
}
private static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter,Set<Class<? extends Annotation>> visitedAnnotationTypes) {if (repeatableContainers == RepeatableContainers.standardRepeatables()) {return standardRepeatablesCache.computeIfAbsent(annotationFilter,key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes);}if (repeatableContainers == RepeatableContainers.none()) {return noRepeatablesCache.computeIfAbsent(annotationFilter,key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes);}return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType, visitedAnnotationTypes);
}

在最后的执行方法中,用到了两个缓存,standardRepeatablesCache 和 noRepeatablesCache,这是 spring 中定义的 Map,ConcurrentReferenceHashMap,key 为 AnnotationFilter,value 为 Cache,这是 AnnotationTypeMappings 中的内部类。也就是说,一个 AnnotationFilter 对应一个 Cache。接着来看看这个 Cache 到底是什么。

// AnnotationTypeMappings$Cache
Cache(RepeatableContainers repeatableContainers, AnnotationFilter filter) {this.repeatableContainers = repeatableContainers;this.filter = filter;this.mappings = new ConcurrentReferenceHashMap<>();
}

这个 Cache 中的 mappings 才是真正的缓存对象,key 为注解类型对应的 Class 对象,value 为 AnnotationTypeMappings。

不管是 standardRepeatablesCache,还是 noRepeatablesCache,获取到 Cache 对象后,调用其 get 方法。

AnnotationTypeMappings get(Class<? extends Annotation> annotationType,Set<Class<? extends Annotation>> visitedAnnotationTypes) {return this.mappings.computeIfAbsent(annotationType, key -> createMappings(key, visitedAnnotationTypes));
}private AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType,Set<Class<? extends Annotation>> visitedAnnotationTypes) {return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType, visitedAnnotationTypes);
}

mappings 缓存中不存在 annotationType 对应的 key,调用 createMappings 进行创建。

private AnnotationTypeMappings(RepeatableContainers repeatableContainers,AnnotationFilter filter, Class<? extends Annotation> annotationType,Set<Class<? extends Annotation>> visitedAnnotationTypes) {this.repeatableContainers = repeatableContainers;this.filter = filter;this.mappings = new ArrayList<>();// 创建 AnnotationTypeMapping,添加进 mappingsaddAllMappings(annotationType, visitedAnnotationTypes);// 校验,清空 AnnotationTypeMapping.claimedAliasesthis.mappings.forEach(AnnotationTypeMapping::afterAllMappingsSet);
}

此处主要关注 addAllMappings 方法,就是在这一步生成了所有的映射。

private void addAllMappings(Class<? extends Annotation> annotationType,Set<Class<? extends Annotation>> visitedAnnotationTypes) {Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes);while (!queue.isEmpty()) {AnnotationTypeMapping mapping = queue.removeFirst();this.mappings.add(mapping);// 添加 meta-annotation addMetaAnnotationsToQueue(queue, mapping);}
}

其实现逻辑:

  1. 将 annotationType 封装成 AnnotationTypeMapping,并将其加入队列
  2. 此时队列不为空,移除刚加入的 AnnotationTypeMapping,将其加入 AnnotationTypeMappings 中字段 mappings
  3. 以此 mapping 为基础,加入其上 meta-annotation 到队列中,然后进行下一次循环

此处处理类似于图的广度优先遍历,借助队列来实现。 

private void addIfPossible(Deque<AnnotationTypeMapping> queue, @Nullable AnnotationTypeMapping source,Class<? extends Annotation> annotationType, @Nullable Annotation ann,Set<Class<? extends Annotation>> visitedAnnotationTypes) {try {queue.addLast(new AnnotationTypeMapping(source, annotationType, ann, visitedAnnotationTypes));}catch (Exception ex) {...}
}

下面来看看 annotationType 到底是如何封装成 AnnotationTypeMapping 的。

AnnotationTypeMapping 的创建

AnnotationTypeMapping(@Nullable AnnotationTypeMapping source, Class<? extends Annotation> annotationType,@Nullable Annotation annotation, Set<Class<? extends Annotation>> visitedAnnotationTypes) {this.source = source;this.root = (source != null ? source.getRoot() : this);this.distance = (source == null ? 0 : source.getDistance() + 1);this.annotationType = annotationType;this.metaTypes = merge(source != null ? source.getMetaTypes() : null,annotationType);this.annotation = annotation;this.attributes = AttributeMethods.forAnnotationType(annotationType);this.mirrorSets = new MirrorSets();this.aliasMappings = filledIntArray(this.attributes.size());this.conventionMappings = filledIntArray(this.attributes.size());this.annotationValueMappings = filledIntArray(this.attributes.size());this.annotationValueSource = new AnnotationTypeMapping[this.attributes.size()];// 解析 AttributeMethods 中每个 method 上的 @AliasFor 注解this.aliasedBy = resolveAliasedForTargets();  // 遍历 attributes 属性方法,判断当前某个 attribute 是否是其它注解的 别名方法processAliases();  addConventionMappings();addConventionAnnotationValues();this.synthesizable = computeSynthesizableFlag(visitedAnnotationTypes);
}

先来看 AttributeMethods 的创建。

AttributeMethods 的创建
static AttributeMethods forAnnotationType(@Nullable Class<? extends Annotation> annotationType) {if (annotationType == null) {return NONE;}// AttributeMethods 中 static 缓存,即同一个注解类型,整个系统中 compute 只执行一次return cache.computeIfAbsent(annotationType, AttributeMethods::compute);
}

此处也用到了缓存,key 为注解类型对应的 Class 对象,value 为 AttributeMethods,同一个系统中,针对同一个 annotationType,compute 方法只执行一次。

private static AttributeMethods compute(Class<? extends Annotation> annotationType) {// 获取注解中定义方法Method[] methods = annotationType.getDeclaredMethods();int size = methods.length;for (int i = 0; i < methods.length; i++) {// 非属性方法置为 nullif (!isAttributeMethod(methods[i])) {methods[i] = null;size--;}}if (size == 0) {return NONE;}// 排序Arrays.sort(methods, methodComparator);// 只复制 属性方法Method[] attributeMethods = Arrays.copyOf(methods, size);return new AttributeMethods(annotationType, attributeMethods);
}// 判断是否是属性方法
private static boolean isAttributeMethod(Method method) {return (method.getParameterCount() == 0 && method.getReturnType() != void.class);
}private AttributeMethods(@Nullable Class<? extends Annotation> annotationType, Method[] attributeMethods) {this.annotationType = annotationType;this.attributeMethods = attributeMethods;this.canThrowTypeNotPresentException = new boolean[attributeMethods.length];boolean foundDefaultValueMethod = false;boolean foundNestedAnnotation = false;for (int i = 0; i < attributeMethods.length; i++) {Method method = this.attributeMethods[i];// 返回值类型Class<?> type = method.getReturnType();if (!foundDefaultValueMethod && (method.getDefaultValue() != null)) {foundDefaultValueMethod = true;}if (!foundNestedAnnotation && (type.isAnnotation() || (type.isArray() && type.getComponentType().isAnnotation()))) {foundNestedAnnotation = true;}ReflectionUtils.makeAccessible(method);// 返回值为以下三种类型才可抛出异常this.canThrowTypeNotPresentException[i] = (type == Class.class || type == Class[].class || type.isEnum());}this.hasDefaultValueMethod = foundDefaultValueMethod;this.hasNestedAnnotation = foundNestedAnnotation;
}

这样就完成了 AttributeMethods 的创建。

接着在 resolveAliasedForTargets 中,遍历刚获得的 AttributeMethods 中的 attributeMethods,处理其上持有的 @AliasFor 注解。

private Map<Method, List<Method>> resolveAliasedForTargets() {Map<Method, List<Method>> aliasedBy = new HashMap<>();for (int i = 0; i < this.attributes.size(); i++) {Method attribute = this.attributes.get(i);// 获取 @AliasFor 实例对象AliasFor aliasFor = AnnotationsScanner.getDeclaredAnnotation(attribute, AliasFor.class);if (aliasFor != null) {Method target = resolveAliasTarget(attribute, aliasFor);// key 是 target 方法,value 是 attribute 方法aliasedBy.computeIfAbsent(target, key -> new ArrayList<>()).add(attribute);}}return Collections.unmodifiableMap(aliasedBy);
}

通过 AnnotationsScanner#getDeclaredAnnotation  来获取属性方法 attribute 上持有的 @AliasFor 注解对象。

// AnnotationScanner
@Nullable
static <A extends Annotation> A getDeclaredAnnotation(AnnotatedElement source, Class<A> annotationType) {// 获取定义的注解,接着遍历,只要有一个与给定 annotationType 类型一致,就将这个 annotation 实例对象返回Annotation[] annotations = getDeclaredAnnotations(source, false);for (Annotation annotation : annotations) {if (annotation != null && annotationType == annotation.annotationType()) {return (A) annotation;}}return null;
}static Annotation[] getDeclaredAnnotations(AnnotatedElement source, boolean defensive) {boolean cached = false;// static 类型,先从缓存获取Annotation[] annotations = declaredAnnotationCache.get(source);if (annotations != null) {cached = true;}else {// 获取 source 上定义的 注解// source 为 @Service 中定义的属性方法时,此时调用 getDeclaredAnnotations 会获取到其方法上定义的注解对象annotations = source.getDeclaredAnnotations();if (annotations.length != 0) {boolean allIgnored = true;// 遍历for (int i = 0; i < annotations.length; i++) {Annotation annotation = annotations[i];// 判断注解对象中注解是否是可忽略的// 举例:针对 @Service,此时 annotation.annotationType() 为 @AliasFor// 调用 AttributeMethods.forAnnotationType 方法,针对 @AliasFor,属性方法有三个if (isIgnorable(annotation.annotationType()) ||!AttributeMethods.forAnnotationType(annotation.annotationType()).isValid(annotation)) {annotations[i] = null;}else {allIgnored = false;}}annotations = (allIgnored ? NO_ANNOTATIONS : annotations);if (source instanceof Class || source instanceof Member) {// 放入缓存declaredAnnotationCache.put(source, annotations);cached = true;}}}if (!defensive || annotations.length == 0 || !cached) {return annotations;}return annotations.clone();
}private static boolean isIgnorable(Class<?> annotationType) {return AnnotationFilter.PLAIN.matches(annotationType);
}boolean isValid(Annotation annotation) {assertAnnotation(annotation);for (int i = 0; i < size(); i++) {if (canThrowTypeNotPresentException(i)) {try {get(i).invoke(annotation);}catch (Throwable ex) {return false;}}}return true;
}

在 AnnotationsScanner#getDeclaredAnnotations 获取 AnnotatedElement 上注解时也采用了缓存,获取时缓存不存在,才调用 java.lang.reflect.AnnotatedElement#getDeclaredAnnotations 去获取注解对象,之后进行过滤,不符合条件的一律置为 null,过滤条件有两个

  1. isIgnore,AnnotationFilter.PLAIN 包含的包名下的注解
  2. 给定注解类型下封装的 AttributeMethods 对于 给定注解 无效

此处第二个过滤条件,调用 AttributeMethods#forAnnotationType,@AliasFor 如果是第一次处理,会调用 compute 方法计算之后,将属性方法放入缓存。

处理完之后,将处理过的注解数组放入缓存。 所以当执行完 AnnotationsScanner#getDeclaredAnnotations 后,每个 source 对应的 annotations 和每个
annotation 对应的 AttributeMethods 都已确定。

获取到 @AliasFor 注解对象之后,调用 resolveAliasTarget,解析属性方法 attribute 上 @AliasFor 注解对应的目标方法。

// AnnotationTypeMapping
private Method resolveAliasTarget(Method attribute, AliasFor aliasFor) {return resolveAliasTarget(attribute, aliasFor, true);
}private Method resolveAliasTarget(Method attribute, AliasFor aliasFor, boolean checkAliasPair) {// @AliasFor 中 value 和 attribute 只能设置一个if (StringUtils.hasText(aliasFor.value()) && StringUtils.hasText(aliasFor.attribute())) {throw ...}// 获取注解类型Class<? extends Annotation> targetAnnotation = aliasFor.annotation();if (targetAnnotation == Annotation.class) {targetAnnotation = this.annotationType;}String targetAttributeName = aliasFor.attribute();if (!StringUtils.hasLength(targetAttributeName)) {targetAttributeName = aliasFor.value();}if (!StringUtils.hasLength(targetAttributeName)) {// @AliasFor value 和 attribute 都未配置,取当前 attribute 方法名称// "value"targetAttributeName = attribute.getName();  }// 获取目标注解中的目标属性// 此时 targetAnnotation,即 @ComponentMethod target = AttributeMethods.forAnnotationType(targetAnnotation).get(targetAttributeName);if (target == null) {if (targetAnnotation == this.annotationType) {throw ...}throw ...}if (target.equals(attribute)) {throw ...}// 返回值类型不相同if (!isCompatibleReturnType(attribute.getReturnType(), target.getReturnType())) {throw ...}if (isAliasPair(target) && checkAliasPair) {AliasFor targetAliasFor = target.getAnnotation(AliasFor.class);if (targetAliasFor != null) {Method mirror = resolveAliasTarget(target, targetAliasFor, false);if (!mirror.equals(attribute)) {throw ...}}}return target;
}

调用 aliasFor.annotation() 方法获取目标注解类 targetAnnotation,接着获取目标属性名,先 attribute(),不存在,再 value(),都未配置,则获取当前属性方法 attribute 的名称,作为目标方法名称。接着 AttributeMethods#forAnnotationType,传入 targetAnnotation,得到 AttributeMethods,接着传入 targetMethodName,得到 targetMethod。

举个例子:

@Service 注解,属性方法:

@AliasFor(annotation = Component.class)
String value() default "";

调用 aliasFor.annotation() 得到 Component.class,这便是目标注解,此处 @AliasFor 中未配置 attribute 和 value,所以拿此属性方法名 value 作为目标方法名,但是又不确定目标注解中是否有此 value 方法,所以调用 AttributeMethods.forAnnotationType(targetAnnotation).get(targetAttributeName),得到目标属性方法,然后进行判断。

获取到目标方法后,放入 aliasedBy,数据结构 Map<Method, List<Method>>,此处将 target 作为 key,原属性方法 attribute 加入 value 对应的 list 集合,之后将 aliasedBy 赋值给 AnnotationTypeMapping 中 aliasedBy 字段。

这样就完成了 annotationType 到 AnnotationTypeMapping 的转换,之后以此 AnnotationTypeMapping 为 source,执行 addMetaAnnotationsToQueue

private void addMetaAnnotationsToQueue(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source) {// 扫描 source 对应注解类型上定义的注解Annotation[] metaAnnotations = AnnotationsScanner.getDeclaredAnnotations(source.getAnnotationType(), false);for (Annotation metaAnnotation : metaAnnotations) {if (!isMappable(source, metaAnnotation)) {continue;}Annotation[] repeatedAnnotations = this.repeatableContainers.findRepeatedAnnotations(metaAnnotation);// 为 nullif (repeatedAnnotations != null) {for (Annotation repeatedAnnotation : repeatedAnnotations) {if (!isMappable(source, repeatedAnnotation)) {continue;}addIfPossible(queue, source, repeatedAnnotation);}}else {addIfPossible(queue, source, metaAnnotation);}}
}

此处又调用到了 AnnotationsScanner#getDeclaredAnnotations,由于前面只在创建 AnnotationTypeMapping 时处理了属性方法,此处当传入 Class 对象时,从缓存获取不到,调用 java.lang.reflect.AnnotatedElement#getDeclaredAnnotations 获取。

举个例子:若为 Service.class,此时获取到其上的四个注解对象,前三个通过 isIgnorable 处理为 null,最后一个为 @Component,通过 AttributeMethods#forAnnotationType 进行处理,前面处理 属性方法上 @AliasFor 时已经处理过 @Component,所以在 AttributeMethods 中会直接返回 @Component 中定义的属性方法,之后 isValid 判断。接着将 Service.class 及其上注解放入 AnnotationsScanner 中 declaredAnnotationCache 缓存。

获取注解 Class 对象上的注解数组后,即 metaAnnotations,对其进行遍历。调用 isMappable 进行判断。

private boolean isMappable(AnnotationTypeMapping source, @Nullable Annotation metaAnnotation) {return (metaAnnotation != null && !this.filter.matches(metaAnnotation) &&!AnnotationFilter.PLAIN.matches(source.getAnnotationType()) &&!isAlreadyMapped(source, metaAnnotation));
}
private boolean isAlreadyMapped(AnnotationTypeMapping source, Annotation metaAnnotation) {// @ComponentClass<? extends Annotation> annotationType = metaAnnotation.annotationType();AnnotationTypeMapping mapping = source;while (mapping != null) {if (mapping.getAnnotationType() == annotationType) {return true;}mapping = mapping.getSource();}return false;
}

还是以 @Service 为例,前三个注解都被处理成 null,只有第四个 @Component,此时执行 isAlreadyMapped,其实就是判断当前注解类型,是否已被处理成了 AnnotationTypeMapping。

接着执行 this.repeatableContainers.findRepeatedAnnotations,之前传入的 repeatableContainers 为 RepeatableContainers.standardRepeatables(),即 RepeatableContainers$StandardRepeatableContainers。

// RepeatableContainers$StandardRepeatableContainers
@Override
@Nullable
Annotation[] findRepeatedAnnotations(Annotation annotation) {Method method = getRepeatedAnnotationsMethod(annotation.annotationType());if (method != null) {return (Annotation[]) ReflectionUtils.invokeMethod(method, annotation);}// 不存在 method,让父类去查找return super.findRepeatedAnnotations(annotation);
}
@Nullable
private static Method getRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) {// 计算缓存起来Object result = cache.computeIfAbsent(annotationType,StandardRepeatableContainers::computeRepeatedAnnotationsMethod);return (result != NONE ? (Method) result : null);
}
private static Object computeRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) {AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType);// 只有一个 value 属性方法if (methods.hasOnlyValueAttribute()) {Method method = methods.get(0);// 判断返回值类型,只有数组类型Class<?> returnType = method.getReturnType();if (returnType.isArray()) {// 注解类型数组,元素类型被 @Repeatable 修饰,此时才将 method 返回Class<?> componentType = returnType.getComponentType();if (Annotation.class.isAssignableFrom(componentType) &&componentType.isAnnotationPresent(Repeatable.class)) {return method;}}}return NONE;
}

 StandardRepeatableContainers 缓存中获取不到,进行 computeRepeatedAnnotationsMethod,只有属性方法名为 value,且只有一个属性方法,返回值为注解类型数组,且注解类型被 @Repeatable 修饰,才将当前属性方法返回,否则一律返回 NONE。

由于返回的 repeatedAnnotations 为 null,所以执行 else 中方法。

// AnnotationTypeMappings
private void addIfPossible(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source, Annotation ann) {addIfPossible(queue, source, ann.annotationType(), ann, new HashSet<>());
}
// 此时将 @Service 作为 source,annotationType 为 @Component
private void addIfPossible(Deque<AnnotationTypeMapping> queue, @Nullable AnnotationTypeMapping source,Class<? extends Annotation> annotationType, @Nullable Annotation ann,Set<Class<? extends Annotation>> visitedAnnotationTypes) {try {queue.addLast(new AnnotationTypeMapping(source, annotationType, ann, visitedAnnotationTypes));}catch (Exception ex) {...}
}

还是以 @Service 举例,此时将 @Service 对应的 AnnotationTypeMapping 作为 source,将 meta-annotation 对应的 @Component,对其创建 AnnotationTypeMapping,并加入队列。

再次调用 AnnotationTypeMapping 构造方法,此时 source 为 @Service 对应的 AnnotationTypeMapping,root 为 @Service 对应的 AnnotationTypeMapping,distance 为 1,AttributeMethods 从缓存获取后直接赋值,resolveAliasedForTargets 时由于 @Component 中属性方法无 @AliasFor 注解,所以不做处理。接着 processAliases。

private void processAliases() {List<Method> aliases = new ArrayList<>();for (int i = 0; i < this.attributes.size(); i++) {aliases.clear();aliases.add(this.attributes.get(i));collectAliases(aliases);if (aliases.size() > 1) {  // 存在别名,加入后 大于1processAliases(i, aliases);}}
}
private void collectAliases(List<Method> aliases) {AnnotationTypeMapping mapping = this;while (mapping != null) {int size = aliases.size();for (int j = 0; j < size; j++) {List<Method> additional = mapping.aliasedBy.get(aliases.get(j));if (additional != null) {aliases.addAll(additional);}}mapping = mapping.source; // 此时存在 source,进行下一次循环}
}

在 collectAliases 方法中,@Component 对应的 AnnotationTypeMapping 中是不含 @AliasFor 的,但其 source 对应的 AnnotationTypeMapping 含有,所以在进行第二次 while 循环时,拿出别名属性方法,加入 aliases 集合,之后执行含参的 processAliases。

private void processAliases(int attributeIndex, List<Method> aliases) {int rootAttributeIndex = getFirstRootAttributeIndex(aliases);AnnotationTypeMapping mapping = this;while (mapping != null) {if (rootAttributeIndex != -1 && mapping != this.root) {for (int i = 0; i < mapping.attributes.size(); i++) {if (aliases.contains(mapping.attributes.get(i))) {// 填充 aliasMappings,赋值mapping.aliasMappings[i] = rootAttributeIndex;}}}mapping.mirrorSets.updateFrom(aliases);mapping.claimedAliases.addAll(aliases);if (mapping.annotation != null) {int[] resolvedMirrors = mapping.mirrorSets.resolve(null,mapping.annotation, ReflectionUtils::invokeMethod);for (int i = 0; i < mapping.attributes.size(); i++) {if (aliases.contains(mapping.attributes.get(i))) {this.annotationValueMappings[attributeIndex] = resolvedMirrors[i];this.annotationValueSource[attributeIndex] = mapping;}}}mapping = mapping.source;}
}private int getFirstRootAttributeIndex(Collection<Method> aliases) {// @Component  root 为 @ServiceAttributeMethods rootAttributes = this.root.getAttributes();for (int i = 0; i < rootAttributes.size(); i++) {// 集合含有,就将 i 返回if (aliases.contains(rootAttributes.get(i))) {return i;}}return -1;
}

此处只是为 AnnotationTypeMapping 构造方法中的一些字段进行填充。

之后,将 @Component 对应的 AnnotationTypeMapping 加入  AnnotationTypeMappings 中 mappings 集合。以此 @Component 对应的 AnnotationTypeMapping 为 source,再次调用 addMetaAnnotationsToQueue。

此时调用 AnnotationsScanner#getDeclaredAnnotations,传入 @Component 对应 Class,缓存不存在,调用 java.lang.reflect.AnnotatedElement#getDeclaredAnnotations,共 4 个注解,前三个处理为 null,第四个 @Indexed,接着加入缓存,key 为 @Component 对应 Class,value 为处理后的注解数组。并且前面对 @Indexed 进行判断时,将其加入了 AttributeMethods 中缓存。

接着调用 addIfPossible,创建 @Indexed 对应的 AnnotationTypeMapping。此时 source 为 @Component 对应的 AnnotationTypeMapping,root 为 @Service 对应的 AnnotationTypeMapping,distance 为 2,计算 synthesizable 标志为 false。之后将其加入 AnnotationTypeMappings 中 mappings 集合。

之后以 @Indexed 对应的 AnnotationTypeMapping 为 source,再次调用 addMetaAnnotationsToQueue。将 @Indexed 对应 Class 加入 AnnotationsScanner 中 declaredAnnotationCache 缓存。@Indexed 上注解都被处理为 null,当不存在 meta-annotation 处理,结束创建添加 AnnotationTypeMapping 的循环。

至此,AnnotationTypeMappings 创建结束,并将创建的 AnnotationTypeMappings 放入 AnnotationTypeMappings$Cache,key 为注解类型对应的 Class 对象,value 为 AnnotationTypeMappings。

接下来,取 AnnotationTypeMappings 中集合 mappings 中第一个 AnnotationTypeMapping,创建 TypeMappedAnnotation。

private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader,@Nullable Object source, @Nullable Object rootAttributes, ValueExtractor valueExtractor,int aggregateIndex, @Nullable int[] resolvedRootMirrors) {this.mapping = mapping;this.classLoader = classLoader;this.source = source;this.rootAttributes = rootAttributes;this.valueExtractor = valueExtractor;this.aggregateIndex = aggregateIndex;this.useMergedValues = true;this.attributeFilter = null;this.resolvedRootMirrors = (resolvedRootMirrors != null ? resolvedRootMirrors :mapping.getRoot().getMirrorSets().resolve(source, rootAttributes, this.valueExtractor));this.resolvedMirrors = (getDistance() == 0 ? this.resolvedRootMirrors :mapping.getMirrorSets().resolve(source, this, this::getValueForMirrorResolution));
}

在 MergedAnnotationReadingVisitor#visitEnd 中创建的 TypeMappedAnnotation 用来填充 SimpleAnnotationMetadataReadingVisitor 中 annotations 字段。当整个 class 文件访问结束时,调用 SimpleAnnotationMetadataReadingVisitor#visitEnd。

// SimpleAnnotationMetadataReadingVisitor
@Override
public void visitEnd() {String[] memberClassNames = StringUtils.toStringArray(this.memberClassNames);MethodMetadata[] annotatedMethods = this.annotatedMethods.toArray(new MethodMetadata[0]);MergedAnnotations annotations = MergedAnnotations.of(this.annotations);this.metadata = new SimpleAnnotationMetadata(this.className, this.access,this.enclosingClassName, this.superClassName, this.independentInnerClass,this.interfaceNames, memberClassNames, annotatedMethods, annotations);
}

此时,针对类上的注解集合,创建 MergedAnnotations,即 MergedAnnotationsCollection 对象。

MergedAnnotationsCollection 的创建

static MergedAnnotations of(Collection<MergedAnnotation<?>> annotations) {return MergedAnnotationsCollection.of(annotations);
}static MergedAnnotations of(Collection<MergedAnnotation<?>> annotations) {Assert.notNull(annotations, "Annotations must not be null");if (annotations.isEmpty()) {return TypeMappedAnnotations.NONE;}return new MergedAnnotationsCollection(annotations);
}private MergedAnnotationsCollection(Collection<MergedAnnotation<?>> annotations) {this.annotations = annotations.toArray(new MergedAnnotation<?>[0]);this.mappings = new AnnotationTypeMappings[this.annotations.length];for (int i = 0; i < this.annotations.length; i++) {MergedAnnotation<?> annotation = this.annotations[i];this.mappings[i] = AnnotationTypeMappings.forAnnotationType(annotation.getType());}
}

可以看到这是一个私有构造方法,只能通过这种方式创建 MergedAnnotationsCollection 对象。在构造方法中,主要是为其中 annotations 和 mappings 两个字段赋值。每一个 MergedAnnotation 对应一个 AnnotationTypeMappings,可以看到此处又调用了 AnnotationTypeMappings#forAnnotationType。此时,从 AnnotationTypeMappings 中对应的容器缓存,此处为 standardRepeatablesCache 中拿到 AnnotationTypeMappings$Cache,接着从 AnnotationTypeMappings$Cache 中 mappings 中拿到缓存的 AnnotationTypeMappings 进行赋值。

这样,就完成了 MergedAnnotationsCollection 对象的创建。

下面来看看创建的这些映射,是怎么使用的。

注解匹配

在基于注解的包扫描模式下,当通过 spring-core 模块下 classreading 包对封装的字节码 Resource 进行访问后,将访问到的信息封装成了 SimpleMetadataReader,之后对 SimpleMetadataReader 进行判断,是候选的组件,才进行 BeanDefinition 的创建。

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {for (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return false;}}for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return isConditionMatch(metadataReader);}}return false;
}

通过 AbstractTypeHierarchyTraversingFilter#match 调用 AnnotationTypeFilter#matchSelf。

@Override
protected boolean matchSelf(MetadataReader metadataReader) {AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();return metadata.hasAnnotation(this.annotationType.getName()) ||(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}

先判断是否直接持有指定注解。

default boolean hasAnnotation(String annotationName) {return getAnnotations().isDirectlyPresent(annotationName);
}// SimpleAnnotationMetadata
@Override
public MergedAnnotations getAnnotations() {return this.annotations;
}
@Override
public boolean isDirectlyPresent(String annotationType) {return isPresent(annotationType, true);
}
private boolean isPresent(Object requiredType, boolean directOnly) {for (MergedAnnotation<?> annotation : this.annotations) {Class<? extends Annotation> type = annotation.getType();if (type == requiredType || type.getName().equals(requiredType)) {return true;}}// 为 true,不考虑 meta-annotationif (!directOnly) {for (AnnotationTypeMappings mappings : this.mappings) {for (int i = 1; i < mappings.size(); i++) {AnnotationTypeMapping mapping = mappings.get(i);if (isMappingForType(mapping, requiredType)) {return true;}}}}return false;
}

不存在,considerMetaAnnotations 为 true,判断 meta-annotation 是否持有指定注解。

// AnnotationMetadata
default boolean hasMetaAnnotation(String metaAnnotationName) {return getAnnotations().get(metaAnnotationName,MergedAnnotation::isMetaPresent).isPresent();
}
@Override
public MergedAnnotations getAnnotations() {return this.annotations;
}@Override
public <A extends Annotation> MergedAnnotation<A> get(String annotationType,@Nullable Predicate<? super MergedAnnotation<A>> predicate) {return get(annotationType, predicate, null);
}
@Override
public <A extends Annotation> MergedAnnotation<A> get(String annotationType,@Nullable Predicate<? super MergedAnnotation<A>> predicate,@Nullable MergedAnnotationSelector<A> selector) {MergedAnnotation<A> result = find(annotationType, predicate, selector);return (result != null ? result : MergedAnnotation.missing());
}
@Nullable
private <A extends Annotation> MergedAnnotation<A> find(Object requiredType,@Nullable Predicate<? super MergedAnnotation<A>> predicate,@Nullable MergedAnnotationSelector<A> selector) {if (selector == null) {// 为 null,赋值selector = MergedAnnotationSelectors.nearest();}MergedAnnotation<A> result = null;for (int i = 0; i < this.annotations.length; i++) {MergedAnnotation<?> root = this.annotations[i];AnnotationTypeMappings mappings = this.mappings[i];for (int mappingIndex = 0; mappingIndex < mappings.size(); mappingIndex++) {AnnotationTypeMapping mapping = mappings.get(mappingIndex);if (!isMappingForType(mapping, requiredType)) {continue;}MergedAnnotation<A> candidate = (mappingIndex == 0 ? (MergedAnnotation<A>) root :TypeMappedAnnotation.createIfPossible(mapping, root, IntrospectionFailureLogger.INFO));if (candidate != null && (predicate == null || predicate.test(candidate))) {if (selector.isBestCandidate(candidate)) {return candidate;}result = (result != null ? selector.select(result, candidate) : candidate);}}}return result;
}private static boolean isMappingForType(AnnotationTypeMapping mapping, @Nullable Object requiredType) {if (requiredType == null) {return true;}Class<? extends Annotation> actualType = mapping.getAnnotationType();return (actualType == requiredType || actualType.getName().equals(requiredType));
}@Nullable
static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(AnnotationTypeMapping mapping, MergedAnnotation<?> annotation, IntrospectionFailureLogger logger) {if (annotation instanceof TypeMappedAnnotation) {TypeMappedAnnotation<?> typeMappedAnnotation = (TypeMappedAnnotation<?>) annotation;return createIfPossible(mapping, typeMappedAnnotation.source,typeMappedAnnotation.rootAttributes,typeMappedAnnotation.valueExtractor,typeMappedAnnotation.aggregateIndex, logger);}return createIfPossible(mapping, annotation.getSource(), annotation.synthesize(),annotation.getAggregateIndex(), logger);
}@Nullable
private static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(AnnotationTypeMapping mapping, @Nullable Object source, @Nullable Object rootAttribute,ValueExtractor valueExtractor, int aggregateIndex, IntrospectionFailureLogger logger) {try {return new TypeMappedAnnotation<>(mapping, null, source, rootAttribute,valueExtractor, aggregateIndex);}catch (Exception ex) {...return null;}
}// AbstractMergedAnnotation
@Override
public boolean isMetaPresent() {return isPresent() && getDistance() > 0;
}// MergedAnnotationSelectors$Nearest
@Override
public boolean isBestCandidate(MergedAnnotation<Annotation> annotation) {return annotation.getDistance() == 0;
}

核心方法就是 MergedAnnotationsCollection#find,首先,选择器不存在,定义一个选择器,用来挑选候选者。接着可以看到,对 MergedAnnotationsCollection 中 annotations 和 mappings 同时进行遍历,annotations 控制外部循环,因为一个 TypeMappedAnnotation 对应一个 AnnotationTypeMappings,对于内部循环,拿遍历到的 mapping,调用 isMappingForType 进行匹配,匹配到了,判断 mappingIndex 是否为 0,为 0,直接将外部循环下的 root 返回,因为之前创建 TypeMappedAnnotation 时,只对 AnnotationTypeMappings 中第一个 AnnotationTypeMapping 创建了 TypeMappedAnnotation。否则调用 TypeMappedAnnotation#createIfPossible 新建一个 TypeMappedAnnotation,作为 candidate,之后利用传入的条件 AbstractMergedAnnotation#isMetaPresent 进行判断,从 meta-annotation 中检索,TypeMappedAnnotation 中持有的 AnnotationTypeMapping 对应的 distance 应大于 0,之后再应用 selector 进行判断,distance 为 0,视为最优候选者,直接返回,否则为 result 赋值,进行下一次循环。

//MergedAnnotationSelectors$Nearest
@Override
public MergedAnnotation<Annotation> select(MergedAnnotation<Annotation> existing, MergedAnnotation<Annotation> candidate) {if (candidate.getDistance() < existing.getDistance()) {return candidate;}return existing;
}

如果再次循环又匹配到了,此时存在多个候选者,应用 selector 进行选择,针对 MergedAnnotationSelectors$Nearest 选择器,就是挑选 distance 最小的,赋值给 result,进行下一次循环。

最后内外循环结束,将 result 返回,不为 null,就是 TypeMappedAnnotation 对象,否则返回一个 MergedAnnotation.missing(),是一个 MissingMergedAnnotation 对象。

之后调用 MergedAnnotation#isPresent,对于 TypeMappedAnnotation 恒为 true,对于 MissingMergedAnnotation 恒为 false,也就是说,一旦返回了 TypeMappedAnnotation,那么肯定是存在指定注解的。

再比如前面 isCandidateComponent 中调用的 isConditionMatch,仍然是采用了注解匹配。

在 ConditionEvaluator#shouldSkip 中,执行了 metadata.isAnnotated(Conditional.class.getName()),判断 metadata 中是否持有 @Conditional 注解。

// AnnotatedTypeMetadata
default boolean isAnnotated(String annotationName) {return getAnnotations().isPresent(annotationName);
}// MergedAnnotationsCollection
@Override
public boolean isPresent(String annotationType) {return isPresent(annotationType, false);
}
private boolean isPresent(Object requiredType, boolean directOnly) {for (MergedAnnotation<?> annotation : this.annotations) {Class<? extends Annotation> type = annotation.getType();if (type == requiredType || type.getName().equals(requiredType)) {return true;}}if (!directOnly) {for (AnnotationTypeMappings mappings : this.mappings) {for (int i = 1; i < mappings.size(); i++) {AnnotationTypeMapping mapping = mappings.get(i);if (isMappingForType(mapping, requiredType)) {return true;}}}}return false;
}

directOnly 为 false,会去检索 meta-annotation,由于前面已经遍历了 annotations,每个 TypeMappedAnnotation 持有 AnnotationTypeMappings 中第一个 AnnotationTypeMapping,所以底下的 AnnotationTypeMappings 中 mappings 的遍历每次从索引 1 开始。

总结

一个 annotationType 对应一个 AnnotationTypeMapping,一个 TypeMappedAnnotation 持有一个 AnnotationTypeMapping;一个 annotationType 对应一个 AnnotationTypeMappings,此时会合并 meta-annotation,多个 TypeMappedAnnotation 对应一个 MergedAnnotations。

映射关系创建完成后,判断是否持有某个注解,就是对 AnnotationTypeMappings 中的 mappings 进行遍历,判断每一个 AnnotationTypeMapping 所对应的注解类型是否与指定注解类型一致。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/51271.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java21的主要新特性总结

目录 概述 变动说明 重要变更和信息 下载地址 Java21新特性总结 1、JEP 441: Switch 的模式匹配&#xff08;正式特性&#xff09; 功能进化 Switch 模式匹配 类型标签 null标签 守卫标签 使用enum常量作值 语法总结 2、JEP 440&#xff1a;Record模式&#xff08…

常用工具类

常用工具类 date类 日期设置方法 方法 描述 setDate() 以数值&#xff08;1-31&#xff09;设置日 setFullYear() 设置年&#xff08;可选月和日&#xff09; setHours() 设置小时&#xff08;0-23&#xff09; setMilliseconds() 设置毫秒&#xff08;0-999&#x…

AOP面向切面编程和log4j的使用(Java版)

什么是面向切面编程 在传统的面向对象编程中&#xff0c;程序的功能被模块化成各个类和方法&#xff0c;这些类和方法分别处理特定的功能。然而&#xff0c;有些功能可能涉及到多个类、多个方法&#xff0c;例如日志记录、事务管理、性能监控等&#xff0c;这些功能可能在不同…

橙单后端项目下载编译遇到的问题与解决

今天下载orange-admin项目&#xff0c;不过下载下来运行出现一些问题。 1、涉及到XMLStreamException的几个类都出现下面的错误 The package javax.xml.stream is accessible from more than one module: <unnamed>, java.xml ctrl-shift-t 可以找到这个引入是哪些包里…

AcWing803. 区间合并

#include<climits>的作用是方便我直接使用INT_MIN,下面这个代码是二刷写的 思路是先根据 [ L , R ] i [L,R]_i [L,R]i​的L先排序&#xff0c;然后遍历vector进行区间合并。 #include<iostream> #include<vector> #include<algorithm> #include<cl…

在window将Redis注册为服务

将redis注册为系统服务&#xff0c;开启自启动 安装服务 默认注册完之后会自动启动&#xff0c;在window中的服务看一下&#xff0c;如果启动类型为自动&#xff0c;状态是自动运行则启动完成。如果是手动&#xff0c;需要右键属性调整为自动&#xff0c;在点击启动&#xff0c…

拉格朗日插值算法一般用几个点比较好

在拉格朗日插值法中&#xff0c;选择适当数量的插值点是确保结果准确性的关键。通常&#xff0c;选择的点数取决于以下几个因素&#xff1a; 点数与多项式的阶数 拉格朗日插值法中&#xff0c;如果你使用 n 个点&#xff0c;则得到的插值多项式是一个 n−1 次多项式。因此&…

【React】详解组件通信:从基础到进阶的全面指南

文章目录 一、父组件向子组件传递数据1. 基本概念2. 示例代码3. 详解定义子组件 Son定义父组件 App导出父组件 App数据流props 的内容 二、子组件向父组件传递数据1. 基本概念2. 示例代码3. 详解引入React库和useState钩子定义子组件 Son定义父组件 App导出父组件 App数据流 三…

代码审计: ThinkPHP V6.0.12LTS反序列化漏洞复现

这里写目录标题 一、前缀知识事件回调&#xff1a; 二、代码审计查找反序列化路由三、利用链分析构造exp 一、前缀知识 事件回调&#xff1a; 概念&#xff1a;在某个特定事件发生时&#xff0c;系统会调用预先定义好的函数&#xff08;即回调函数&#xff09;来处理该事件。…

C++11新特性——智能指针——参考bibi《 原子之音》的视频以及ChatGpt

智能指针 一、内存泄露1.1 内存泄露常见原因1.2 如何避免内存泄露 二、实例Demo2.1 文件结构2.2 Dog.h2.3 Dog.cpp2.3 mian.cpp 三、独占式智能指针:unique _ptr3.1 创建方式3.1.1 ⭐从原始(裸)指针转换&#xff1a;3.1.2 ⭐⭐使用 new 关键字直接创建&#xff1a;3.1.3 ⭐⭐⭐…

Python信号量Semaphore

Python信号量Semaphore 在Python中,Semaphore(信号量)是一种同步原语,用于控制对共享资源的访问。它主要用于限制同时访问某个资源或资源池的线程或进程的数量,类似于操作系统中的信号量概念。 使用 Semaphore 的基本方法 导入 Semaphore: from threading import Semap…

nginx代理服务配置,基于http协议-Linux(CentOS)

基于http协议的nginx代理服务 1. 打开 Nginx 虚拟机80端口配置文件2. 添加代理配置3. 重启nginx服务 nginx代理缓存配置 1. 打开 Nginx 虚拟机80端口配置文件 Nginx 的默认80端口虚拟机配置文件通常位于/etc/nginx/conf.d/default.conf。 vim /etc/nginx/conf.d/default.con…

Idea中连接MS SQL Server报错:驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接

一、错误重现 报错如下&#xff1a; [08S01] 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to request…

leetcode日记(59)简化路径

&#xff08;小杯文字描述…看了好一会才看懂题目要求&#xff09;题目是标准化路径&#xff0c;就是将多个‘/’优化为一个、将最后一个‘/’去掉、将‘/../’和‘/./’去掉&#xff0c;将原路径转化为实际想表达的最终路径。 有点像单纸带图灵机&#xff0c;需要依次遍历字母…

Windows系统安全加固方案:快速上手系统加固指南 (下)

这里写目录标题 一、概述二、IP协议安全配置启用SYN攻击保护 三、文件权限3.1 关闭默认共享3.2 查看共享文件夹权限3.3 删除默认共享 四、服务安全4.1禁用TCP/IP 上的NetBIOS4.2 ### 禁用不必要的服务 五、安全选项5.1启动安全选项5.2禁用未登录前关机 六、其他安全配置**6.1防…

《JavaEE》----2.<多线程的简介创建Thread类>

前言&#xff1a; 大家好&#xff0c;我目前在学习java。我准备利用这个暑假&#xff0c;来复习之前学过的内容&#xff0c;并整理好之前写过的博客进行发布。如果博客中有错误或者没有读懂的地方。热烈欢迎大家在评论区进行讨论&#xff01;&#xff01;&#xff01; 喜欢我文…

【深度学习】“复杂场景下基于深度学习的卷积神经网络在鸟类多类别识别中的模型设计与性能优化研究“(中)

【深度学习】“复杂场景下基于深度学习的卷积神经网络在鸟类多类别识别中的模型设计与性能优化研究”(中) 大家好 我是寸铁&#x1f44a; 【深度学习】“复杂场景下基于深度学习的卷积神经网络在鸟类多类别识别中的模型设计与性能优化研究”(中)✨ 喜欢的小伙伴可以点点关注 &a…

Qt 实战(3)数据类型 | 3.3、QString

文章目录 一、QString1、创建和初始化 QString2、字符串拼接3、字符串的查找和替换4、字符串的分割5、字符串的转换6、字符串的格式化7、国际化支持 前言&#xff1a; QString 是 Qt 框架中用于处理 Unicode 字符串的一个非常强大且灵活的类。它提供了丰富的功能来操作文本数据…

一个网站搞定Adobe系列软件下载安装,良心网站!

Adobe系列软件几乎是每个办公职场人都会用到的软件&#xff0c;比如PDF&#xff0c;PS&#xff0c;AI&#xff0c;PE&#xff0c;PR等&#xff0c;不管你是设计图片&#xff0c;制作编辑音频还是视频&#xff0c;Adobe都有对应的软件。但是对于大部分用户来说&#xff0c;Adobe…

小A点菜

题目描述 小A口袋里只剩 M 元 (M≤10000)&#xff0c;来到一家餐馆点菜吃饭。 餐馆有 N(N≤100) 种菜品&#xff0c;但每种菜只有一份&#xff0c;其中第 i 种售价 ai​ 元 (ai​≤1000) 。 小A奉行 “不把钱花光就不罢休” 的原则&#xff0c;所以他点的菜一定刚好把身上的…