Spring 元信息详解

举报
龙哥手记 发表于 2022/11/22 15:22:53 2022/11/22
【摘要】 《读尽源码 第三十四篇》

ClassMetadata

public interface ClassMetadata {

    /**
     * 类名
     */
    String getClassName();

    /**
     * 是否是接口
     */
    boolean isInterface();

    /**
     * 是否是注解
     */
    boolean isAnnotation();

    /**
     * 是否是超类
     */
    boolean isAbstract();

    /**
     * 是否允许创建,实例化
     */
    default boolean isConcrete() {
        return !(isInterface() || isAbstract());
    }

    /**
     * 是否有final修饰
     */
    boolean isFinal();

    /**
     * 是否独立
     */
    boolean isIndependent();

    /**
     * 是否有内部类
     */
    default boolean hasEnclosingClass() {
        return (getEnclosingClassName() != null);
    }

    /**
     * 是否是基础类
     */
    @Nullable
    String getEnclosingClassName();

    /**
     * 是否有父类
     */
    default boolean hasSuperClass() {
        return (getSuperClassName() != null);
    }

    /**
     * 父类名称
     */
    @Nullable
    String getSuperClassName();

    /**
     * 实现类名称列表
     */
    String[] getInterfaceNames();

    /**
     * 成员列表
     * @since 3.1
     */
    String[] getMemberClassNames();

}Copy to clipboardErrorCopied

AnnotatedTypeMetadata

public interface AnnotatedTypeMetadata {

    /**
     * 获取所有注解
     */
    MergedAnnotations getAnnotations();

    /**
     * 是否有注解
     */
    default boolean isAnnotated(String annotationName) {
        return getAnnotations().isPresent(annotationName);
    }

    /**
     * 获取注解的属性
     */
    @Nullable
    default Map<String, Object> getAnnotationAttributes(String annotationName) {
        return getAnnotationAttributes(annotationName, false);
    }
    // 省略其他

}Copy to clipboardErrorCopied

AnnotationMetadata

public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata {

   /**
     * 获取注解名称,全类名
    */
   default Set<String> getAnnotationTypes() {
      return getAnnotations().stream()
            .filter(MergedAnnotation::isDirectlyPresent)
            .map(annotation -> annotation.getType().getName())
            .collect(Collectors.toCollection(LinkedHashSet::new));
   }

   /**
     * 注解全类名
    */
   default Set<String> getMetaAnnotationTypes(String annotationName) {
      MergedAnnotation<?> annotation = getAnnotations().get(annotationName, MergedAnnotation::isDirectlyPresent);
      if (!annotation.isPresent()) {
         return Collections.emptySet();
      }
      return MergedAnnotations.from(annotation.getType(), SearchStrategy.INHERITED_ANNOTATIONS).stream()
            .map(mergedAnnotation -> mergedAnnotation.getType().getName())
            .collect(Collectors.toCollection(LinkedHashSet::new));
   }

   /**
     * 是否包含某个注解
    */
   default boolean hasAnnotation(String annotationName) {
      return getAnnotations().isDirectlyPresent(annotationName);
   }

   /**
     * 是否被某个注解标记过
    */
   default boolean hasMetaAnnotation(String metaAnnotationName) {
      return getAnnotations().get(metaAnnotationName,
            MergedAnnotation::isMetaPresent).isPresent();
   }

   /**
     * 是否有注解,类里面有一个注解就返回true
    */
   default boolean hasAnnotatedMethods(String annotationName) {
      return !getAnnotatedMethods(annotationName).isEmpty();
   }

   /**
     * 获取包含注解的方法
    */
   Set<MethodMetadata> getAnnotatedMethods(String annotationName);


   /**
     * 通过反射创建一个注解的元信息
    */
   static AnnotationMetadata introspect(Class<?> type) {
      return StandardAnnotationMetadata.from(type);
   }

}Copy to clipboardErrorCopied

MethodMetadata

public interface MethodMetadata extends AnnotatedTypeMetadata {

    /**
     * 方法名称
     */
    String getMethodName();

    /**
     * 方法全路径
     */
    String getDeclaringClassName();

    /**
     * 返回值类型
     */
    String getReturnTypeName();

    /**
     * 是不是abstrac修饰
     */
    boolean isAbstract();

    /**
     * 是否 static 修饰
     */
    boolean isStatic();

    /**
     * 是否 final 修饰
     */
    boolean isFinal();

    /**
     * 是否重载
     */
    boolean isOverridable();

}Copy to clipboardErrorCopied

MetadataReader

public interface MetadataReader {

    /**
     * Return the resource reference for the class file.
     *
     * 获取资源
     */
    Resource getResource();

    /**
     * Read basic class metadata for the underlying class.
     * 获取类的元信息
     */
    ClassMetadata getClassMetadata();

    /**
     * Read full annotation metadata for the underlying class,
     * including metadata for annotated methods.
     *
     * 获取注解的元信息
     */
    AnnotationMetadata getAnnotationMetadata();

}Copy to clipboardErrorCopied

MetadataReaderFactory

  • 用来创建 MetadataReader
public interface MetadataReaderFactory {

    /**
     * Obtain a MetadataReader for the given class name.
     * @param className the class name (to be resolved to a ".class" file)
     * @return a holder for the ClassReader instance (never {@code null})
     * @throws IOException in case of I/O failure
     */
    MetadataReader getMetadataReader(String className) throws IOException;

    /**
     * Obtain a MetadataReader for the given resource.
     * @param resource the resource (pointing to a ".class" file)
     * @return a holder for the ClassReader instance (never {@code null})
     * @throws IOException in case of I/O failure
     */
    MetadataReader getMetadataReader(Resource resource) throws IOException;

}Copy to clipboardErrorCopied
  • 接口解释的差不多了.接下来看一些实现

StandardClassMetadata

  • 通过 JAVA 反射来获取类的元信息

  • 这个类采用的方式是 Java class 的方法,通过构造方法来填写一个 Class 对象. 之后使用这个对象的方法

public class StandardClassMetadata implements ClassMetadata {

   private final Class<?> introspectedClass;

   @Deprecated
   public StandardClassMetadata(Class<?> introspectedClass) {
      Assert.notNull(introspectedClass, "Class must not be null");
      this.introspectedClass = introspectedClass;
   }

   /**
    * Return the underlying Class.
    */
   public final Class<?> getIntrospectedClass() {
      return this.introspectedClass;
   }


   @Override
   public String getClassName() {
      return this.introspectedClass.getName();
   }

   @Override
   public boolean isInterface() {
      return this.introspectedClass.isInterface();
   }

   @Override
   public boolean isAnnotation() {
      return this.introspectedClass.isAnnotation();
   }

   @Override
   public boolean isAbstract() {
      return Modifier.isAbstract(this.introspectedClass.getModifiers());
   }

   @Override
   public boolean isFinal() {
      return Modifier.isFinal(this.introspectedClass.getModifiers());
   }

   @Override
   public boolean isIndependent() {
      return (!hasEnclosingClass() ||
            (this.introspectedClass.getDeclaringClass() != null &&
                  Modifier.isStatic(this.introspectedClass.getModifiers())));
   }

   @Override
   @Nullable
   public String getEnclosingClassName() {
      Class<?> enclosingClass = this.introspectedClass.getEnclosingClass();
      return (enclosingClass != null ? enclosingClass.getName() : null);
   }

   @Override
   @Nullable
   public String getSuperClassName() {
      Class<?> superClass = this.introspectedClass.getSuperclass();
      return (superClass != null ? superClass.getName() : null);
   }

   @Override
   public String[] getInterfaceNames() {
      Class<?>[] ifcs = this.introspectedClass.getInterfaces();
      String[] ifcNames = new String[ifcs.length];
      for (int i = 0; i < ifcs.length; i++) {
         ifcNames[i] = ifcs[i].getName();
      }
      return ifcNames;
   }

   @Override
   public String[] getMemberClassNames() {
      LinkedHashSet<String> memberClassNames = new LinkedHashSet<>(4);
      for (Class<?> nestedClass : this.introspectedClass.getDeclaredClasses()) {
         memberClassNames.add(nestedClass.getName());
      }
      return StringUtils.toStringArray(memberClassNames);
   }

}Copy to clipboardErrorCopied

StandardMethodMetadata

  • 通过 java 反射获取方法的元信息
  • 构造方法传递一个 method
    • 如果这个方法有注解,会进行注解的解析
public class StandardMethodMetadata implements MethodMetadata {

   private final Method introspectedMethod;

   private final boolean nestedAnnotationsAsMap;

   private final MergedAnnotations mergedAnnotations;

   @Deprecated
   public StandardMethodMetadata(Method introspectedMethod) {
      this(introspectedMethod, false);
   }


   @Deprecated
   public StandardMethodMetadata(Method introspectedMethod, boolean nestedAnnotationsAsMap) {
      Assert.notNull(introspectedMethod, "Method must not be null");
      this.introspectedMethod = introspectedMethod;
      this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
      this.mergedAnnotations = MergedAnnotations.from(
            introspectedMethod, SearchStrategy.DIRECT, RepeatableContainers.none());
   }


   @Override
   public MergedAnnotations getAnnotations() {
      return this.mergedAnnotations;
   }

   /**
    * Return the underlying Method.
    */
   public final Method getIntrospectedMethod() {
      return this.introspectedMethod;
   }

   @Override
   public String getMethodName() {
      return this.introspectedMethod.getName();
   }

   @Override
   public String getDeclaringClassName() {
      return this.introspectedMethod.getDeclaringClass().getName();
   }

   @Override
   public String getReturnTypeName() {
      return this.introspectedMethod.getReturnType().getName();
   }

   @Override
   public boolean isAbstract() {
      return Modifier.isAbstract(this.introspectedMethod.getModifiers());
   }

   @Override
   public boolean isStatic() {
      return Modifier.isStatic(this.introspectedMethod.getModifiers());
   }

   @Override
   public boolean isFinal() {
      return Modifier.isFinal(this.introspectedMethod.getModifiers());
   }

   @Override
   public boolean isOverridable() {
      return !isStatic() && !isFinal() && !isPrivate();
   }

   private boolean isPrivate() {
      return Modifier.isPrivate(this.introspectedMethod.getModifiers());
   }

   @Override
   @Nullable
   public Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
      if (this.nestedAnnotationsAsMap) {
         return MethodMetadata.super.getAnnotationAttributes(annotationName, classValuesAsString);
      }
      return AnnotatedElementUtils.getMergedAnnotationAttributes(this.introspectedMethod,
            annotationName, classValuesAsString, false);
   }

    /**
     * 获取所有注解的信息
     */
   @Override
   @Nullable
   public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) {
      if (this.nestedAnnotationsAsMap) {
         return MethodMetadata.super.getAllAnnotationAttributes(annotationName, classValuesAsString);
      }
      return AnnotatedElementUtils.getAllAnnotationAttributes(this.introspectedMethod,
            annotationName, classValuesAsString, false);
   }

}Copy to clipboardErrorCopied

StandardAnnotationMetadata

  • StandardAnnotationMetadata 是 StandardClassMetadata 的子类

  • 还是一个基于 JAVA 反射做的一个类

  • 获取注解属性 map

@Override
@Nullable
public Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
   if (this.nestedAnnotationsAsMap) {
      return AnnotationMetadata.super.getAnnotationAttributes(annotationName, classValuesAsString);
   }
   return AnnotatedElementUtils.getMergedAnnotationAttributes(
         getIntrospectedClass(), annotationName, classValuesAsString, false);
}Copy to clipboardErrorCopied
  • org.springframework.core.annotation.AnnotatedElementUtils#getMergedAnnotationAttributes(java.lang.reflect.AnnotatedElement, java.lang.String, boolean, boolean)
    • org.springframework.core.annotation.AnnotatedElementUtils#getAnnotationAttributes
      • org.springframework.core.annotation.MergedAnnotation#asAnnotationAttributes
@Nullable
public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
        String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {

    MergedAnnotation<?> mergedAnnotation = getAnnotations(element)
            .get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared());
    return getAnnotationAttributes(mergedAnnotation, classValuesAsString, nestedAnnotationsAsMap);
}Copy to clipboardErrorCopied
  • 查看这个方法的源码借助测试类org.springframework.core.annotation.AnnotatedElementUtilsTests#getMergedAnnotationAttributesOnClassWithLocalAnnotation
  • getAnnotations() 方法

getAnnotations

  • org.springframework.core.annotation.MergedAnnotations#from(java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.RepeatableContainers)

    • org.springframework.core.annotation.TypeMappedAnnotations#from(java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.RepeatableContainers, org.springframework.core.annotation.AnnotationFilter)
  • 最终我们找到的代码如下

static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
      RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {

   if (AnnotationsScanner.isKnownEmpty(element, searchStrategy)) {
      return NONE;
   }
   return new TypeMappedAnnotations(element, searchStrategy, repeatableContainers, annotationFilter);
}Copy to clipboardErrorCopied
  • 判断是否为空. 为空返回 None,不会为空构造出一个对象 org.springframework.core.annotation.TypeMappedAnnotations

MergedAnnotations

public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>> {

    //确定注解是否存在
    <A extends Annotation> boolean isPresent(Class<A> annotationType);
    //注解是否直接存在
    <A extends Annotation> boolean isDirectlyPresent(Class<A> annotationType);
    // 获取匹配的注解
    <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType);
        // 省略其他

}Copy to clipboardErrorCopied
  • 这个接口中还有两个方法

    1. of 将多个MergedAnnotations合并

    2. from

      将多个注解合并

SearchStrategy

enum SearchStrategy {

   /**
    * Find only directly declared annotations, without considering
    * {@link Inherited @Inherited} annotations and without searching
    * superclasses or implemented interfaces.
        *
        * 直接查找
    */
   DIRECT,

   /**
    * Find all directly declared annotations as well as any
    * {@link Inherited @Inherited} superclass annotations. This strategy
    * is only really useful when used with {@link Class} types since the
    * {@link Inherited @Inherited} annotation is ignored for all other
    * {@linkplain AnnotatedElement annotated elements}. This strategy does
    * not search implemented interfaces.
        *
        * 继承查找
    */
   INHERITED_ANNOTATIONS,

   /**
    * Find all directly declared and superclass annotations. This strategy
    * is similar to {@link #INHERITED_ANNOTATIONS} except the annotations
    * do not need to be meta-annotated with {@link Inherited @Inherited}.
    * This strategy does not search implemented interfaces.
        * 查找当前类和父类的注解
    */
   SUPERCLASS,

   /**
    * Perform a full search of the entire type hierarchy, including
    * superclasses and implemented interfaces. Superclass annotations do
    * not need to be meta-annotated with {@link Inherited @Inherited}.
    */
   TYPE_HIERARCHY,

   /**
    * Perform a full search of the entire type hierarchy on the source
    * <em>and</em> any enclosing classes. This strategy is similar to
    * {@link #TYPE_HIERARCHY} except that {@linkplain Class#getEnclosingClass()
    * enclosing classes} are also searched. Superclass annotations do not
    * need to be meta-annotated with {@link Inherited @Inherited}. When
    * searching a {@link Method} source, this strategy is identical to
    * {@link #TYPE_HIERARCHY}.
    */
   TYPE_HIERARCHY_AND_ENCLOSING_CLASSES
}Copy to clipboardErrorCopied
  • org.springframework.core.annotation.TypeMappedAnnotations#get(java.lang.String, java.util.function.Predicate<? super org.springframework.core.annotation.MergedAnnotation<A>>, org.springframework.core.annotation.MergedAnnotationSelector<A>)
@Override
public <A extends Annotation> MergedAnnotation<A> get(String annotationType,
      @Nullable Predicate<? super MergedAnnotation<A>> predicate,
      @Nullable MergedAnnotationSelector<A> selector) {
       // 匹配校验
   if (this.annotationFilter.matches(annotationType)) {
      return MergedAnnotation.missing();
   }
   MergedAnnotation<A> result = scan(annotationType,
         new MergedAnnotationFinder<>(annotationType, predicate, selector));
   return (result != null ? result : MergedAnnotation.missing());
}Copy to clipboardErrorCopied

Scan

org.springframework.core.annotation.AnnotationsScanner#scan(C, java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.AnnotationsProcessor<C,R>, java.util.function.BiPredicate<C,java.lang.Class<?>>)

@Nullable
static <C, R> R scan(C context, AnnotatedElement source, SearchStrategy searchStrategy,
      AnnotationsProcessor<C, R> processor, @Nullable BiPredicate<C, Class<?>> classFilter) {

   R result = process(context, source, searchStrategy, processor, classFilter);
   return processor.finish(result);
}Copy to clipboardErrorCopied

在这个里面重点关注PROCESS方法

@Nullable
private static <C, R> R process(C context, AnnotatedElement source,
      SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor,
      @Nullable BiPredicate<C, Class<?>> classFilter) {

   if (source instanceof Class) {
      return processClass(context, (Class<?>) source, searchStrategy, processor, classFilter);
   }
   if (source instanceof Method) {
      return processMethod(context, (Method) source, searchStrategy, processor, classFilter);
   }
   return processElement(context, source, processor, classFilter);
}Copy to clipboardErrorCopied

测试类

    @Transactional("TxConfig")
    static class TxConfig {
    }Copy to clipboardErrorCopied

显然这是一个类他会走processClass方法

  • 根据扫描方式进行扫描
@Nullable
private static <C, R> R processClass(C context, Class<?> source,
      SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor,
      @Nullable BiPredicate<C, Class<?>> classFilter) {

   switch (searchStrategy) {
      case DIRECT:
         return processElement(context, source, processor, classFilter);
      case INHERITED_ANNOTATIONS:
         return processClassInheritedAnnotations(context, source, searchStrategy, processor, classFilter);
      case SUPERCLASS:
         return processClassHierarchy(context, source, processor, classFilter, false, false);
      case TYPE_HIERARCHY:
         return processClassHierarchy(context, source, processor, classFilter, true, false);
      case TYPE_HIERARCHY_AND_ENCLOSING_CLASSES:
         return processClassHierarchy(context, source, processor, classFilter, true, true);
   }
   throw new IllegalStateException("Unsupported search strategy " + searchStrategy);
}Copy to clipboardErrorCopied
  • 扫描的形式就不贴出完整代码了

finish就包装一下返回.

  • 此时org.springframework.core.annotation.AnnotatedElementUtils#getMergedAnnotationAttributes(java.lang.reflect.AnnotatedElement, java.lang.String, boolean, boolean)这个方法走到了最后一步org.springframework.core.annotation.AnnotatedElementUtils#getAnnotationAttributes

  • 最后的组装 map 方法

    org.springframework.core.annotation.TypeMappedAnnotation#asMap(java.util.function.Function<org.springframework.core.annotation.MergedAnnotation<?>,T>, org.springframework.core.annotation.MergedAnnotation.Adapt...)

@Override
public AnnotationAttributes asAnnotationAttributes(Adapt... adaptations) {
   return asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()), adaptations);
}Copy to clipboardErrorCopied
@Override
public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, Adapt... adaptations) {
   T map = factory.apply(this);
   Assert.state(map != null, "Factory used to create MergedAnnotation Map must not return null");
   AttributeMethods attributes = this.mapping.getAttributes();
   for (int i = 0; i < attributes.size(); i++) {
      Method attribute = attributes.get(i);
      Object value = (isFiltered(attribute.getName()) ? null :
            getValue(i, getTypeForMapOptions(attribute, adaptations)));
      if (value != null) {
         map.put(attribute.getName(),
               adaptValueForMapOptions(attribute, value, map.getClass(), factory, adaptations));
      }
   }
   return map;
}Copy to clipboardErrorCopied
  • 获取属性列表,循环, 放入 map 返回.

    map

    ​ key: 注解的函数

    ​ value: 函数对应的值

@Transactional("TxConfig")Copy to clipboardErrorCopied
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Inherited
@interface Transactional {

   String value() default "";

   String qualifier() default "transactionManager";

   boolean readOnly() default false;
}Copy to clipboardErrorCopied

如果是上面这样的结构那么返回值为

value:TxConfig
qulifiter:transactionManager
readOnlay:falseCopy to clipboardErrorCopied

SimpleMetadataReader

  • 构造方法传递三个参数直接使用

    final class SimpleMetadataReader implements MetadataReader {
    
       private static final int PARSING_OPTIONS = ClassReader.SKIP_DEBUG
             | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES;
    
       private final Resource resource;
    
       private final AnnotationMetadata annotationMetadata;
    
    Copy to clipboardErrorCopied
 SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
    SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
    getClassReader(resource).accept(visitor, PARSING_OPTIONS);
    this.resource = resource;
    this.annotationMetadata = visitor.getMetadata();
 }

 private static ClassReader getClassReader(Resource resource) throws IOException {
    try (InputStream is = new BufferedInputStream(resource.getInputStream())) {
       try {
          return new ClassReader(is);
       }
       catch (IllegalArgumentException ex) {
          throw new NestedIOException("ASM ClassReader failed to parse class file - " +
                "probably due to a new Java class file version that isn't supported yet: " + resource, ex);
       }
    }
 }


 @Override
 public Resource getResource() {
    return this.resource;
 }

 @Override
 public ClassMetadata getClassMetadata() {
    return this.annotationMetadata;
 }

 @Override
 public AnnotationMetadata getAnnotationMetadata() {
    return this.annotationMetadata;
 }Copy to clipboardErrorCopied

}


## SimpleMetadataReaderFactory

- 关注点为如何获取`MetadataReader`
1. 通过资源直接 new 出来
2. 通过 className 转换成资源地址,
3. 将资源地址转换成`Resource`对象
4. new 出来

```java
@Override
public MetadataReader getMetadataReader(String className) throws IOException {
 try {
    String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX +
          ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX;
    Resource resource = this.resourceLoader.getResource(resourcePath);
    return getMetadataReader(resource);
 }
 catch (FileNotFoundException ex) {
    // Maybe an inner class name using the dot name syntax? Need to use the dollar syntax here...
    // ClassUtils.forName has an equivalent check for resolution into Class references later on.
    int lastDotIndex = className.lastIndexOf('.');
    if (lastDotIndex != -1) {
       String innerClassName =
             className.substring(0, lastDotIndex) + '$' + className.substring(lastDotIndex + 1);
       String innerClassResourcePath = ResourceLoader.CLASSPATH_URL_PREFIX +
             ClassUtils.convertClassNameToResourcePath(innerClassName) + ClassUtils.CLASS_FILE_SUFFIX;
       Resource innerClassResource = this.resourceLoader.getResource(innerClassResourcePath);
       if (innerClassResource.exists()) {
          return getMetadataReader(innerClassResource);
       }
    }
    throw ex;
 }
}

@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
 return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。