Java自定义注解入门到实战

4,552 阅读4分钟
  • 什么是注解?

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 比如我们常见的@Override和@Deprecated都是注解,注解可以加在类、方法、成员变量等上面,类似于给他们“打标签"。

  • 注解怎么定义?

public @interface 注解名{} 看起来和定义接口很相似只是多了一个@符号 接口: public interface 接口名 注解: public @interface 注解名

public @interface lkx {
    
}
  • 注解怎么使用?

现在我们注解已经定义好了,使用的时候直接“@注解名”就可以使用了 比如下面我们可以定义在“类、成员变量、成员方法”上:

@lkx
public class Test {
    @lkx
    private int num;
    
    @lkx
    public static void main(String[] args) {
        System.out.println("hello");
    }
}
  • 思考一下

现在注解定义好也已经使用了,但是我不想定义到类上和成员方法上,只想定义在成员方法上,如何才能让注解只能定义到方法上,定义到别的地方报错呢? 想要的效果: 在这里插入图片描述

这个时候就要使用元注解来限定范围了。

  • 元注解

元注解通俗的来说就是定义在注解上的注解,在Java中有四个元注解 @Target @Retention @Documented @Inherited

  • @Target

@Target就是用于描述注解的定义范围,可以限制这个注解定义的元素类型。

参数作用
ElementType.ANNOTATION_TYPE可以应用于注解类型
ElementType.CONSTRUCTOR可以应用于构造函数
ElementType.FIELD可以应用于字段或属性
ElementType.LOCAL_VARIABLE可以应用于局部变量
ElementType.METHOD可以应用于方法级注解
ElementType.PACKAGE可以应用于包声明
ElementType.PARAMETER可以应用于方法的参数
ElementType.TYPE可以应用于类的任何元素

因为我们要限制只想定义在成员变量上,所以我们应该使用ElementType.FIELD

@Target(ElementType.FIELD)
public @interface lkx {

}

但是如果我们想同时定义在成员变量和成员方法上应该怎么办呢? 多个参数只需要用大括号包起来,逗号隔开就可以了

@Target({ElementType.FIELD,ElementType.METHOD})
public @interface lkx {

}

现在就只有定义在类上的注解报错了 在这里插入图片描述

  • @Retention

@Retention是用于定义注解的生命周期,也可以理解为存储方式。

参数作用
RetentionPolicy.SOURCE标记的注解仅保留在源级别中,并被编译器忽略
RetentionPolicy.CLASS标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略
RetentionPolicy.RUNTIME标记的注解由 JVM 保留,因此运行时环境可以使用它

下面两个元注解使用不多,暂时不做详解

  • @Documented

@Documented是用于描述生成帮助文档时是否要保留其注解信息。

  • @Inherited

@Inherited是用于描述使被它修饰的注解是否具有继承性。

  • 注解元素

上面我们只是定义了一个注解,但是不能传任何信息,只是相当于一个标签,现在我们看看要怎么给注解定义参数:

@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface lkx {
    String name() default "张三"; //可以使用default定义默认的值
    int age();
}

现在我们看一下如何传参:

public class Test {

    //name有默认值,也可以不写
    @lkx(name = "李四",age = 18)
    private int num;
    
    public static void main(String[] args) {
        System.out.println("hello");
    }
}
  • 实战

现在注解也定义好了,参数也传入了,你是否还在想注解有什么卵用。。。现在我们有个小需求,把注解传入的参数赋值到成员变量上。 例如:

@lkx(name = "李四",age = 18)
private int num; //num没有被赋值,等于0

赋值完成后
num = 18

注意事项:

  1. 下面代码需要用到反射,如果还不会的小伙伴可以看我前面几篇文章。
  2. 因为要用反射拿到注解参数,所以@Retention需要定义为RetentionPolicy.RUNTIME

实现代码:

@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface lkx {
    String name() default "张三";
    int age();
}
public class Test {

    //name有默认值,也可以不写
    @lkx(name = "李四",age = 18)
    private static int num;

    @lkx(name = "王五",age = 38)
    private static int age;

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        System.out.println("赋值前: num: " + num+"   age: "+age);

        //拿到类的字节码
        Class<Test> testClass = Test.class;
        //拿到所有成员变量
        for (Field declaredField : testClass.getDeclaredFields()) {
            //检测成员变量上是否有@lkx注解
            if (declaredField.isAnnotationPresent(lkx.class)) {
                lkx annotation = declaredField.getAnnotation(lkx.class);
                //获取到注解中的age的值
                int age = annotation.age();
                declaredField.set(testClass.newInstance(),age);
            }
        }

        System.out.println("赋值后: num: " + num+"   age: "+age);
    }
}

运行结果:

赋值前: num: 0   age: 0
赋值后: num: 18   age: 38
  • 思考

如果是Android开发的小伙伴,是否觉得上面的实战很眼熟?如果不眼熟那看一下下面的代码:

@BindView(R.id.groupChat)
Button mGroupChat;

@BindView(R.id.privateChat)
Button mPrivateChat;

如果把上面的实战研究明白,你应该能很轻松的写一个通过注解动态findViewById的框架了吧,不过不建议项目中使用,因为反射会影响代码执行效率,如果想要高效率的执行,建议看看butterknife框架源码是怎么实现的。