注解的简单操作

定义注解

元注解:

  1. @Target标记当前注解的作用范围
  2. @Retention标记当前注解在什么情况下生效。注解的生命周期。SOURCE:源码中保存,编译的时候移除。CLASS:源码,编译后class文件保留,jvm加载的时候移除。RUNNING:源码,class文件,jvm中都保存。
  3. @Inherited当前注解是否可以被(继承)。例如:如果一个类上标注了带有@Inherited注解,然后它的子类继承它,也具有这个注解。
  4. @Documented是否可以记录到文档

通过@Interface定义注解。并标注元注解。

package com.example.AnnotionTest;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
    String name() default "";
}

通过@Interface定义注解。可继承的注解。标注元注解@Inherited。定义一个father类,一个son类。son类继承father类。

package com.example.AnnotionTest;

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TestAnnotation2 {
    String value();
}

  定义一个Father类,被标注@TestAnnotation2注解。@TestAnnotation2注解中有@Inherited注解。

package com.example.AnnotionTest;

@TestAnnotation2(value = "I'm Father")
public class Father {
}

  定义一个Son类,继承Father类。看看Son类是否能获取注解@TestAnnotation2

package com.example.AnnotionTest;

public class Son extends Father {
    public static void main(String[] args) {
        Son son = new Son();
        TestAnnotation2 annotation = son.getClass().getAnnotation(TestAnnotation2.class);
        System.out.println("son上获取的注解:" + annotation);
    }
}

在这里插入图片描述

定义一个注解,标注其他注解。属性可以通过注解@AliasFor进行关联。

package com.example.AnnotionTest;

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@TestAnnotation2(value = "")
@Documented
public @interface TestAnnotation3 {
    String address() default "";
    @AliasFor(annotation = TestAnnotation2.class,attribute = "value")
    String value();
}

测试过滤一个类中标注注解的方法

  重点关注@TestAnnotation3,@TestAnnotation2注解。通过AnnotatedElementUtils.getMergedAnnotation方法的获取情况。

package com.example.AnnotionTest;

import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.reflect.Method;
import java.util.Map;

public class AnnotionTest2 {
    @TestAnnotation
    public void test1(){
        System.out.println("我是test1");
    }
    @TestAnnotation2(value = "test2")
    public void test2(){
        System.out.println("我是test2");
    }

    @TestAnnotation3(value = "test3")
    public void test3(){
        System.out.println("我是test3");
    }

    public static void main(String[] args) {
        try {
            Map<Method, TestAnnotation> methodTestAnnotationMap = MethodIntrospector.selectMethods(AnnotionTest2.class, new MethodIntrospector.MetadataLookup<TestAnnotation>() {
                @Override
                public TestAnnotation inspect(Method method) {
                    return AnnotatedElementUtils.getMergedAnnotation(method, TestAnnotation.class);
                }
            });

            Map<Method, TestAnnotation2> methodTestAnnotationMap2 = MethodIntrospector.selectMethods(AnnotionTest2.class, new MethodIntrospector.MetadataLookup<TestAnnotation2>() {
                @Override
                public TestAnnotation2 inspect(Method method) {
                    return AnnotatedElementUtils.getMergedAnnotation(method, TestAnnotation2.class);
                }
            });

            Map<Method, TestAnnotation3> methodTestAnnotationMap3 = MethodIntrospector.selectMethods(AnnotionTest2.class, new MethodIntrospector.MetadataLookup<TestAnnotation3>() {
                @Override
                public TestAnnotation3 inspect(Method method) {
                    return AnnotatedElementUtils.getMergedAnnotation(method, TestAnnotation3.class);
                }
            });

            System.out.println(methodTestAnnotationMap);
            System.out.println(methodTestAnnotationMap2);
            System.out.println(methodTestAnnotationMap3);

        }catch(Exception e) {
            System.out.println(e);
        }

    }
}

在这里插入图片描述
  可以发现。过滤标注注解的方法。对于@TestAnnotation2,可以获取到标注@TestAnnotation2,@TestAnnotation3注解标注的方法。说明,AnnotatedElementUtils.getMergedAnnotation方法获取注解标注的方法,会向下取它的子注解

  将AnnotatedElementUtils.getMergedAnnotation替换成AnnotationUtils.getAnnotation。输入结果如下。也可以过滤出来方法,但是注解上的值是空。因为AnnotatedElementUtils支持属性覆盖
在这里插入图片描述

AnnotatedElementUtils与AnnotationUtils的区别

  spring为开发人员提供了两个搜索注解的工具类,分别是AnnotatedElementUtils和AnnotationUtils。

AnnotationUtils

  用于处理注解,处理元注解,桥接方法(编译器为通用声明生成)以及超方法(用于可选注解继承)的常规使用程序方法。

AnnotatedElements

  用于在AnnotatedElements上查找注解,元注解和可重复注解的常规实用程序方法。AnnotatedElementUtils为Spring的元注解编程模型定义了公共API,并支持注解属性覆盖。如果您不需要支持注解属性覆盖,请考虑使用AnnotationUtils。
  支持@Inherited
  属性覆盖指的是注解的一个成员覆盖另一个成员,最后两者成员属性值一致。

获取类中的注解属性

idea快捷键查看一个类的所有方法

快捷键:ALT + 7

在这里插入图片描述

MethodIntrospector

  MethodIntrospector是Spring-core包中的一个工具类。它定义了搜索元数据相关的方法。通常用于查找注解的方法。

  MetadataLookup是一个函数式接口。对于给定的方法执行查找并返回关联的元数据。

在这里插入图片描述

AnnotatedElementUtils

  AnnotatedElementUtils注解元素工具类。它为Spring的元注解变成模型定义了公共API。并支持注解属性覆盖。如果不需要支持注解属性覆盖,请考虑使用AnnotionUtils
  AnnotatedElementUtils提供了两类查找。
  ①get语义的查找
    get语义的查找可查找直接定义在AnnotatedElement(比如方法,类等)上的,或继承来的注解。
  ②find语义的查找
    如果AnnotatedElement是类,则搜索这个类的超类和接口。
    如果AnnotatedElement是方法,则搜索这个方法的桥接方法,以及超类和接口中的方法。
  get和find方法都支持@Inherited
getMergedAnnotationAttributes
getMergedAnnotation
getAllMergedAnnotations
getMergedRepeatableAnnotations
findMergedAnnotationAttributes
findMergedAnnotation
findAllMergedAnnotations
findMergedRepeatableAnnotations

xxl-job中的应用

Map<Method, XxlJob> annotatedMethods = null;   // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
            try {
                annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                        new MethodIntrospector.MetadataLookup<XxlJob>() {
                            @Override
                            public XxlJob inspect(Method method) {
                                return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                            }
                        });
            } catch (Throwable ex) {
                logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
            }

  在xxl-job的源码中。通过MethodIntrospector的selectMethods方法查找一个类的元数据。AnnotatedElementUtils.findMerged
Annotation()方法查找xxl-job注解标注的Method。

总结

  新增注解A上标注@Inherited。A标注父类,子类继承父类,那么在spring中子类可以通过AnnotationUtils或者AnnotatedElmentUtils获取上标注在父类上的注解A。
  AnnotationUtils与AnnotatedElmentUtils的区别,AnnotatedElmentUtils支持属性覆盖,其他的暂不了解。
在这里插入图片描述
在这里插入图片描述

补充。@Inherited

  通过jdk原生注解api,和spring提供的api。验证注解@Inherited。

jdk原生api

注解不标注@Inherited注解。定义接口上,通过jdk原生api获取。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestOne {
    String oneName() default "";
    String one() default  "";
}
// 注解标注在接口类上
@TestOne
public interface Eat {
}

// 类实现接口
public class Parent implements Eat {
}

// 子类继承父类
public class Son extends Parent{
}

public class TestInHerited {
    public static void main(String[] args) {
        TestOne testOne = Eat.class.getAnnotation(TestOne.class);
        TestOne parentTestOne = Parent.class.getAnnotation(TestOne.class);
        TestOne sonTestOne = Son.class.getAnnotation(TestOne.class);

        System.out.println(testOne);
        System.out.println(parentTestOne);
        System.out.println(sonTestOne);
    }
}

在这里插入图片描述
  结论:注解上没有标注@Inherited注解。注解标注在接口上。jdk原生api:接口实现类,接口实现类的子类获取不到注解。

注解标注@Inherited注解。定义接口上,通过jdk原生api获取。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TestOne {
    String oneName() default "";
    String one() default  "";
}
public @interface TestOne {
    String oneName() default "";
    String one() default  "";
}
// 注解标注在接口类上
@TestOne
public interface Eat {
}

// 类实现接口
public class Parent implements Eat {
}

// 子类继承父类
public class Son extends Parent{
}

public class TestInHerited {
    public static void main(String[] args) {
        TestOne testOne = Eat.class.getAnnotation(TestOne.class);
        TestOne parentTestOne = Parent.class.getAnnotation(TestOne.class);
        TestOne sonTestOne = Son.class.getAnnotation(TestOne.class);

        System.out.println(testOne);
        System.out.println(parentTestOne);
        System.out.println(sonTestOne);
    }
}

在这里插入图片描述

   结论:注解上增加注解@Inherited。结果一样。

注解不标注@Inherited注解。定义类上,通过jdk原生api获取。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestOne {
    String oneName() default "";
    String one() default  "";
}

public interface Eat {
}

// 类实现接口。// 注解标注在类上
@TestOne 
public class Parent implements Eat {
}

// 子类继承父类
public class Son extends Parent{
}

public class TestInHerited {
    public static void main(String[] args) {
        TestOne testOne = Eat.class.getAnnotation(TestOne.class);
        TestOne parentTestOne = Parent.class.getAnnotation(TestOne.class);
        TestOne sonTestOne = Son.class.getAnnotation(TestOne.class);

        System.out.println(testOne);
        System.out.println(parentTestOne);
        System.out.println(sonTestOne);
    }
}

在这里插入图片描述
  结论:jdk原生API。只有注解标注的类可以获取到注解。

注解标注@Inherited注解。定义类上,通过jdk原生api获取。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TestOne {
    String oneName() default "123";
    String one() default  "456";
}

public interface Eat {
}

// 类实现接口。// 注解标注在类上
@TestOne 
public class Parent implements Eat {
}

// 子类继承父类
public class Son extends Parent{
}

public class TestInHerited {
    public static void main(String[] args) {
        TestOne testOne = Eat.class.getAnnotation(TestOne.class);
        TestOne parentTestOne = Parent.class.getAnnotation(TestOne.class);
        TestOne sonTestOne = Son.class.getAnnotation(TestOne.class);

        System.out.println(testOne);
        System.out.println(parentTestOne);
        System.out.println(sonTestOne);
    }
}

在这里插入图片描述
  结论:jdk原生api,@Inherited注解只有标注在类上。子类才会获取到注解

jdk原生api,@Inherited总结

  @Inherited注解的含义是继承。在jdk原生api中。只有标注到类上,子类才能获取到注解。标注到接口上,实现类获取不到注解

spring框架提供的注解处理类

注解标注@Inherited注解。定义类上

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TestOne {
    String oneName() default "123";
    String one() default  "456";
}

public interface Eat {
}

// 类实现接口。// 注解标注在类上
@TestOne 
public class Parent implements Eat {
}

// 子类继承父类
public class Son extends Parent{
}

public class TestInHerited {
    public static void main(String[] args) {
         /* TestOne testOne = Eat.class.getAnnotation(TestOne.class);
        TestOne parentTestOne = Parent.class.getAnnotation(TestOne.class);
        TestOne sonTestOne = Son.class.getAnnotation(TestOne.class);*/

        TestOne testOne = AnnotationUtils.findAnnotation(Eat.class, TestOne.class);
        TestOne parentTestOne = AnnotationUtils.findAnnotation(Parent.class, TestOne.class);
        TestOne sonTestOne = AnnotationUtils.findAnnotation(Son.class, TestOne.class);

        System.out.println(testOne);
        System.out.println(parentTestOne);
        System.out.println(sonTestOne);
    }
}

在这里插入图片描述
  结论:和jdk原生api一样。子类可以获取父类上的注解。

注解不标注@Inherited注解。定义类上

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestOne {
    String oneName() default "123";
    String one() default  "456";
}

public interface Eat {
}

// 类实现接口。// 注解标注在类上
@TestOne 
public class Parent implements Eat {
}

// 子类继承父类
public class Son extends Parent{
}

public class TestInHerited {
    public static void main(String[] args) {
         /* TestOne testOne = Eat.class.getAnnotation(TestOne.class);
        TestOne parentTestOne = Parent.class.getAnnotation(TestOne.class);
        TestOne sonTestOne = Son.class.getAnnotation(TestOne.class);*/

        TestOne testOne = AnnotationUtils.findAnnotation(Eat.class, TestOne.class);
        TestOne parentTestOne = AnnotationUtils.findAnnotation(Parent.class, TestOne.class);
        TestOne sonTestOne = AnnotationUtils.findAnnotation(Son.class, TestOne.class);

        System.out.println(testOne);
        System.out.println(parentTestOne);
        System.out.println(sonTestOne);
    }
}

在这里插入图片描述
  结论:即使不标注注解@Inherited,子类依然可以获取到父类上标注的注解。

注解标注@Inherited注解。定义接口上

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TestOne {
    String oneName() default "123";
    String one() default  "456";
}
@TestOne 
public interface Eat {
}

// 类实现接口。// 注解标注在类上
// @TestOne 
public class Parent implements Eat {
}

// 子类继承父类
public class Son extends Parent{
}

public class TestInHerited {
    public static void main(String[] args) {
         /* TestOne testOne = Eat.class.getAnnotation(TestOne.class);
        TestOne parentTestOne = Parent.class.getAnnotation(TestOne.class);
        TestOne sonTestOne = Son.class.getAnnotation(TestOne.class);*/

        TestOne testOne = AnnotationUtils.findAnnotation(Eat.class, TestOne.class);
        TestOne parentTestOne = AnnotationUtils.findAnnotation(Parent.class, TestOne.class);
        TestOne sonTestOne = AnnotationUtils.findAnnotation(Son.class, TestOne.class);

        System.out.println(testOne);
        System.out.println(parentTestOne);
        System.out.println(sonTestOne);
    }
}

在这里插入图片描述
结论:接口实现类,子类都可以获取到接口上标注的注解。

注解不标注@Inherited注解。定义接口上

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestOne {
    String oneName() default "123";
    String one() default  "456";
}
@TestOne 
public interface Eat {
}

// 类实现接口。// 注解标注在类上
// @TestOne 
public class Parent implements Eat {
}

// 子类继承父类
public class Son extends Parent{
}

public class TestInHerited {
    public static void main(String[] args) {
         /* TestOne testOne = Eat.class.getAnnotation(TestOne.class);
        TestOne parentTestOne = Parent.class.getAnnotation(TestOne.class);
        TestOne sonTestOne = Son.class.getAnnotation(TestOne.class);*/

        TestOne testOne = AnnotationUtils.findAnnotation(Eat.class, TestOne.class);
        TestOne parentTestOne = AnnotationUtils.findAnnotation(Parent.class, TestOne.class);
        TestOne sonTestOne = AnnotationUtils.findAnnotation(Son.class, TestOne.class);

        System.out.println(testOne);
        System.out.println(parentTestOne);
        System.out.println(sonTestOne);
    }
}

在这里插入图片描述
结论:注解上不标注@Inherited。接口实现类,子类也可以获取到接口上标注的注解。

spring框架的api,@Inherited总结

  spring框架提供的工具类,操作注解。属性@Inherited没有意义。接口实现类,子类都可以获取到注解信息。

补充。@AliasFor

同一个注解内,属性设置别名

public @interface AliasTest{
      @AliasFor("locations")
      String[] value() default {};
  
      @AliasFor("value")
      String[] locations() default {};
   }

  使用限制:
  (1)别名都是成对出现,为value属性设置locations别名,需要含有locations属性并为其设置value别名;
  (2)成对出现的属性返回值相同;
  (3)必须为成对出现的属性必须设置默认值;
  (4)成对出现的属性的默认值必须相同;
  (5)AliasFor注解不能设置annotation属性

注解内,设置其他注解的属性别名

public @interface AliasTestOne{
      String[] one() default {};
 }
   
@AliasTestOne 
public @interface AliasTestTwo{
      @AliasFor(annotation = AliasTestOne.class, attribute = "one")
      String[] value() default {};
 
      String[] locations() default {};
   }

  使用限制:
  (1)AliasFor中定义的annotation属性必须为该注解元注解;
  (2)AliasFor中定义的attribute属性的值必须为annotation属性指定的元注解中的属性;

补充。AnnotationUtils 与 AnnotatedElementUtils的简单比较

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@TestTwo
public @interface TestOne {
    @AliasFor(annotation = TestTwo.class,attribute = "twoName")
    String oneName() default "123";
    String one() default  "456";
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestTwo {
    String twoName() default "abc";
    String two() default  "def";
}

@TestOne(oneName = "哈哈")
public interface Eat {
}

public class TestInHerited {
    public static void main(String[] args) {
        TestTwo annotation = AnnotationUtils.findAnnotation(Eat.class, TestTwo.class);
        System.out.println(annotation);

        TestTwo mergedAnnotation = AnnotatedElementUtils.findMergedAnnotation(Eat.class, TestTwo.class);
        System.out.println(mergedAnnotation);
    }
}

在这里插入图片描述
  可以发现,通过注解@TestOne设置oneName属性,别名对应的@TestTwo的twoName属性。
  AnnotationUtils获取注解@TestTwo的属性,还是@TestTwo自己默认的属性
  AnnotatedElementUtils获取注解@TestTwo的属性,获取到的是@TestOne中别名oneName设置的属性

补充。MethodIntrospector方法内省的使用

// 定义注解TestMethodOne ,元注解上配置注解TestMethodTwo 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@TestMethodTwo
public @interface TestMethodOne {
    String methodOne() default "";
}

// 定义注解TestMethodTwo
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestMethodTwo {
    String methodTwo() default "";
}

// 定义方法测试类。testMethod方法标注注解TestMethodOne,testMehtodThree方法标注注解TestMethodTwo
public class MethodTest {
    @TestMethodOne(methodOne = "哈哈哈哈")
    public void testMethod() {
        System.out.println("我是测试方法testMethod注解嘞");
    }

    public void testMethodTwo() {
        System.out.println("我是测试方法testMethodTwo注解嘞");
    }

    @TestMethodTwo(methodTwo = "嘻嘻嘻嘻")
    public void testMethodThree() {
        System.out.println("testMethodThree");
    }
}

// 验证。过滤标注注解TestMethodOne的方法。过滤标注注解TestMethodTwo的方法
public class TestMethod {
    public static void main(String[] args) {
        Map<Method, TestMethodTwo> methodTwo = MethodIntrospector.selectMethods(MethodTest.class, new MethodIntrospector.MetadataLookup<TestMethodTwo>() {
            @Override
            public TestMethodTwo inspect(Method method) {
                return AnnotationUtils.findAnnotation(method, TestMethodTwo.class);
            }
        });

        Map<Method, TestMethodOne> methodOne = MethodIntrospector.selectMethods(MethodTest.class, new MethodIntrospector.MetadataLookup<TestMethodOne>() {
            @Override
            public TestMethodOne inspect(Method method) {
                return AnnotationUtils.findAnnotation(method, TestMethodOne.class);
            }
        });

        System.out.println(methodTwo);
        System.out.println(methodOne);
    }
}

在这里插入图片描述
  可以发现,通过MethodIntrospector的selectMethod方法可以按照注解过滤对应的方法。
  过滤注解TestMethodTwo方法,返回的是两个方法。testMethodThree,testMethod。testMethod方法上标注的@TestMethodOne注解,元注解中有@TestMethodTwo注解。但是@TestMethodTwo属性确实空的。可以通过上面介绍的AnnotatedElementUtils的方法获取属性。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@TestMethodTwo
public @interface TestMethodOne {
    // 定义了别名
    @AliasFor(annotation = TestMethodTwo.class,attribute = "methodTwo")
    String methodOne() default "";
}

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestMethodTwo {
    String methodTwo() default "";
}

public class MethodTest {
    @TestMethodOne(methodOne = "哈哈哈哈")
    public void testMethod() {
        System.out.println("我是测试方法testMethod注解嘞");
    }

    public void testMethodTwo() {
        System.out.println("我是测试方法testMethodTwo注解嘞");
    }

    @TestMethodTwo(methodTwo = "嘻嘻嘻嘻")
    public void testMethodThree() {
        System.out.println("testMethodThree");
    }
}

public class TestMethod {
    public static void main(String[] args) {
      /*  Map<Method, TestMethodTwo> methodTwo = MethodIntrospector.selectMethods(MethodTest.class, new MethodIntrospector.MetadataLookup<TestMethodTwo>() {
            @Override
            public TestMethodTwo inspect(Method method) {
                return AnnotationUtils.findAnnotation(method, TestMethodTwo.class);
            }
        });

        Map<Method, TestMethodOne> methodOne = MethodIntrospector.selectMethods(MethodTest.class, new MethodIntrospector.MetadataLookup<TestMethodOne>() {
            @Override
            public TestMethodOne inspect(Method method) {
                return AnnotationUtils.findAnnotation(method, TestMethodOne.class);
            }
        });*/
        Map<Method, TestMethodTwo> methodTwo = MethodIntrospector.selectMethods(MethodTest.class, new MethodIntrospector.MetadataLookup<TestMethodTwo>() {
            @Override
            public TestMethodTwo inspect(Method method) {
                 // 通过AnnotatedElementUtils获取注解
                return AnnotatedElementUtils.findMergedAnnotation(method, TestMethodTwo.class);
            }
        });

        Map<Method, TestMethodOne> methodOne = MethodIntrospector.selectMethods(MethodTest.class, new MethodIntrospector.MetadataLookup<TestMethodOne>() {
            @Override
            public TestMethodOne inspect(Method method) {
                 // 通过AnnotatedElementUtils获取注解
                return AnnotatedElementUtils.findMergedAnnotation(method, TestMethodOne.class);
            }
        });

        System.out.println(methodTwo);
        System.out.println(methodOne);
    }
}

在这里插入图片描述
  可以发现,过滤出来的方法,注解@TestMethodOne中的methodOne属性与@TestMethodTwo中的methodTwo一致。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值