Java-注解学习

注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

一、创建一个注解

定义一个注解使用**@interface**进行声明:

@Target({ElementType.TYPE,ElementType.FIELD})//@Target:声明注解位置
@Retention(RetentionPolicy.SOURCE)//@Retention:声明保留级别
public @interface MyAnnotation {
    //使用时需要添加参数:@MyAnnotation(id = 1)
    int id();
    //多个元素时:@MyAnnotation(id = 1, key = "12")
    String key();
    //添加default默认返回值,未声明则默认XXX。例:@MyAnnotation(id = 1, key = "12")
    //也可以设置新的值@MyAnnotation(id = 1, key = "12",value = "123")
    String value() default "XXX";
}

使用创建的注解Lance:

@MyAnnotation(id = 1, key = "12")
public class AnnotationMain {}

二、元注解

元注解,可以称为注解上的注解,用来对注解类型进行注解的注解类。在JDK 1.5中提供了4个标准的元注解。分别如下:

@Retention 、@Target、@Documented、@Inherited

@Retention注解

标识注解的保留级别,也可以是被保留的时间。定义Annotation时,@Retention注解是可有可无,没有则默认取RetentionPolicy.CLASS
保留级别取值如下:

RetentionPolicy.SOURCE,//仅保留在源码中,在编译期间会被编译器自动忽略,即class文件中无法查找到该注解
RetentionPolicy.CLASS,//保留在class文件中,在编译期间会被编译器保留。但会被JVM给忽略
RetentionPolicy.RUNTIME//可以被JVM保留,在运行期间可以通过反射进行获取

创建三个不同级别的注解,同时使用:

@Retention(RetentionPolicy.SOURCE)
public @interface SourcePolicy {}
@Retention(RetentionPolicy.CLASS)
public @interface ClassPolicy {}
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimePolicy {}

public class MyPolicy {
    @SourcePolicy
    public void mySource(){ }
    @ClassPolicy
    public void myClass(){ }
    @RuntimePolicy
    public void myRuntime(){ }
}

查看被编译后的MyPolicy.class文件,可以发现mySource没有生成相应的注解信息:

  public mySource()V

  public myClass()V
  @Lcom/example/annotation_app/policy/ClassPolicy;() // invisible
  
  public myRuntime()V
  @Lcom/example/annotation_app/policy/RuntimePolicy;()

由此可得出其保留等级:RUNTIME>CLASS>SOURCE。RUNTIME包含SOURCE和CLASS,CLASS包含SOURCE。因此注解与保留等级是一对一的关系

@Target注解

注解的类型属性,声明其使用范围,是可以修饰在类、成员属性还是方法等等。具体声明属性在ElementType中查看,一个target可以配置多个ElementType属性。如果没有定义@Target,则注解可在任何位置进行修饰。

>   ElementType.TYPE, 					// 在类、接口或者枚举类
    ElementType.FIELD,					// 成员属性即成员变量,(包含:枚举常量)
    ElementType.METHOD,					// 成员方法
    ElementType.PARAMETER,				// 方法参数
    ElementType.CONSTRUCTOR,			// 构造方法
    ElementType.LOCAL_VARIABLE,			// 局部变量
    ElementType.ANNOTATION_TYPE,		// 注解类型声明
    ElementType.PACKAGE,				// 包声明
    ElementType.TYPE_PARAMETER,			// 类型参数,JDK 1.8 新增
    ElementType.TYPE_USE				// 使用类型的任何地方,JDK 1.8 新增

代码如下:

@Target(ElementType.TYPE)//只能在类上注明
public @interface TypeTarget {}
@Target({ElementType.FIELD,ElementType.METHOD})//只能在成员属性和成员方法上注明
public @interface FieldOrMethodTarget {}
@TypeTarget
public class MyTarget {
    @FieldOrMethodTarget
    public String param;
    @FieldOrMethodTarget
    public int id;
    @FieldOrMethodTarget
    public void method(@ParamterOrLocalVariableTarget String param){}
}

三、注解应用场景

按照@Retention 元注解定义的注解存储方式,注解可以被在三种场景下使用:
1、RetentionPolicy.SOURCE级别的应用场景,由于该级别只保留在源码中,通过javac编译成class文件后就不存在了,所以一般应用于APT技术与IDE语法检查

  • APT技术,全称Annotation Processor Tools,即注解处理工具。具体是在javac编译期间根据具体的方法生成代码的一种技术。
  • APT工具的使用和创建:生成一个java lib的工程包,在该工程中创建一个类继承AbstractProcessor,即声明此类是一个注解处理程序,代码如下:
@SupportedAnnotationTypes("com.example.annotation_app.MyAnnotation")
public class MyProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Messager messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE, "===========print message MyAnnotation");
        return false;
    }
}

@SupportedAnnotationTypes:声明指定要处理的注解的全路径,声明后会只处理MyAnnotation的注解信息。
process:javac将所有收集到标本MyAnnotation注解信息在此处进行操作。此处可以打印日志(如上代码即打印日志),请求http等操作。一般都是在这里生成新的类或其他文件来改变原有程序的执行方式。

声明一个注解处理器后,我们还需要对该处理器去进行注册,注册位置通常是在java lib工程的main目录下生成一个resources\META-INF\services\目录,并在此目录下生成一个javax.annotation.processing.Processor文件。并在此文件中写明自己定义的注解处理器的全路径。如下图所示:
在这里插入图片描述
最后在你的Android项目工程build.gradle文件中annotationProcessor依赖这个java lib工程即可完成。

	dependencies {
	    annotationProcessor project(':java_lib')
	}

当你在编译你的Android工程时,javac读取完对应的注解代码后就会自动调用注解处理器的process方法。

  • IDEA语法检查

举例:当我们在某一个方法中需要指定传入的参数为我们所定义的类型,如果不是则去进行提示。
举一种常见写法:

 	public  static  final int SUCCESS = 1;
    public  static  final int FAIL = 0;
    public static void setState(int value){}
    public static void main(String[] args) {
        setState(SUCCESS);
        setState(5);
    }

很明显我们可以看到setState方式是想我们传入指定的SUCCESS和FAIL参数。当然我们也可以传入0或者1,但是同样我们也可以传入4、6、7、8等等,虽然编译器不会报错,且开发工具不会有任何警告,但是对我们后期的维护会造成一定的难度。

当然我们也可以用枚举去完成参数的限定,代码如下:

 	enum State{ SUCCESS,FAIL }
    public static void setState(State value){ }
    public static void main(String[] args) {
        setState(State.SUCCESS);
    }

Java(enum)的实质是一种特殊单例的静态成员变量,在运行期间每一个枚举元素都会去生成一个对象,会被全部加载入内存中,占用一定的内存,对性能会有一定的影响。因此我们可以使用注解去完成这一个语法检查功能。

创建一个语法检查注解:

	@IntDef({MyGrammar.SUCCESS,MyGrammar.FAIL})
    @Target({ElementType.FIELD,ElementType.PARAMETER})
    @Retention(RetentionPolicy.SOURCE)
    @interface GrammarChecks { }
  • @IntDef:这是由android为我们提供的语法检查元注解,在里面声明要使用的引用常量。如果使用的是未声明的引用常量,IDEA工具会给我们引用的参数添加红色的下划线错误提示(仅做提示,不会影响编译报错)。

代码如下:

public class MyGrammar {
    public  static  final int SUCCESS = 1;
    public  static  final int FAIL = 0;
    public static void setState(@GrammarChecks int value){}
    public static void main(String[] args) {
        setState(SUCCESS);
        setState(10);//此处入参下会显示红色的下划线警告
    }
}

2、RetentionPolicy.CLASS字节码级别注解,这个级别会被编译到class文件中,但是会被虚拟机忽略(即无法在运行中通过反射获取)。所以需要我们通过class文件去编写代码逻辑,用注解去标注是否可以修改的区分或者不同逻辑的判断。此时完全符合此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。

3、RetentionPolicy.RUNTIME 包含前两种功能,且能被JVM读取,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。

  • 说到注解与反射技术,早期使用的ButterKnife(黄油刀)就是使用的注解与反射技术
@Target(ElementType.FIELD)//声明注解类型为成员属性
@Retention(RetentionPolicy.RUNTIME)//声明注解信息保留运行期
public @interface ButterKnife {
    @IdRes int value();//声明一个value用来存储id信息
}

@IdRes 也是一个元注解,是有Android提供的。用于检测此value的传入格式,若不是在R文件中注册过的id就会报错。

在Activity中使用这个注解:

public class MainActivity extends AppCompatActivity {
    @ButterKnife(R.id.title)
    public TextView title;
}

此时该注解只是存储了R.id.title,还没有给title进行赋值,所以执行会报错。
通过反射技术给title进行赋值,代码如下:

public class ReflexUtil {
    public static void initReflex(Activity activity){
        //获取到activity的class对象
        Class<? extends Activity> aClass = activity.getClass();
        //获取到activity所有的成语属性
        Field[] declaredFields = aClass.getDeclaredFields();
        //循环遍历数组
        for (Field field: declaredFields){
            //校验成员属性是否被声明ButterKnife注解
            if (field.isAnnotationPresent(ButterKnife.class)){
                //获取到该成员属性声明的注解对象
                ButterKnife annotation = field.getAnnotation(ButterKnife.class);
                //获取注解携带的value值
                int value = annotation.value();
                //通过id获取到实例对象
                View viewById = activity.findViewById(value);
                //设置private权限也可以被修改
                field.setAccessible(true);
                try {
                    //给成员属性赋值
                    field.set(activity, viewById);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

最后还需要在Activity的onCreate中进行初始化:

public class MainActivity extends AppCompatActivity {
    @ButterKnife(R.id.title)
    public TextView title;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ReflexUtil.initReflex(this);
        title.setText("注解值");
    }
}
									                                                 方知~
																				2021-03-07
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值