这是我参与更文挑战的第2天,活动详情查看: 更文挑战
1、注解是什么?
Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制,注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据,注解对它们注解的代码没有直接影响。
怎么理解呢?
可以理解成字面意思,他就是个注解,用来注释用的,和商场里的标签一样,标记这个东西是黄瓜,标记这个是西瓜似得
我们看看Java里怎么自定义一个注解?
2、注解的定义
Java中所有的注解,默认实现Annotation接口:
package java.lang.annotation;
public interface Annotation {
boolean equals(Object var1);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
复制代码
3、元注解
元注解:在定义注解时,注解类也能够使用其它的注解声明,对注解类型进行注解的注解类,我们称之为元注解(meta-annotation),一般我们在定义自定义注解时,需要指定的元注解有两个
@Documented和@Inherited这两个元注解,前者用于被Javadoc工具提取成文档,后者表示允许子类集成父类中定义的注解
@Target
注解标记另一个注解,以限制可以应用注解的Java元素类型。目标注解指定以下元素类型之一作为其值
- ElementType.TYPE 用于类的任何元素(类 、接口,注解,枚举)
- ElementType.FIELD 应用于字段或属性
- ElementType.METHOD 应用于方法级注解
- ElementType.PARAMETER 应用于方法的参数
- ElementType.CONSTRUCTOR 用用于构造方法
- ElementType.LOCAL_VARIABLE 应用于局部变量
- ElementType.ANNOTATION_TYPE 应用于注解类型
- ElementType.PACKAGE 应用于包声明
@ Retention
注解指定标记注解的存储方式,指定注解的生效时期就用这个注解
- RetentionPolicy.SOURCE 标记注解仅保留在 源码级别中,并被编译器忽略
- RetentionPolicy.CLASS 标记注解在编译时由编译器保留,单JVM会忽略
- RetentionPolicy.RUNTIME 便捷的注解由JVM保留,因此运行时环境可以使用它
编译器保留的时候 ,源码级别的时期也是能使用的,而不是单单在编译时被使用,RUNTIME的时候同理,RUNTIME时期包含了SOURCE时期和CLASS时期
4、自定义一个注解
在元注解中,我们看到,允许在使用注解时传递参数,我们也能在自定义注解中传递参数(让自定义注解的主体包含annotation type element 注解类型元素声明),看起来很像方法,可以定义可选的默认值
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS) //注解的保留时期 到编译期
@Target({ElementType.TYPE,ElementType.FIELD})//应用与类 的元素 和 属性 字段
public @interface BindView {
String value();//没有默认值
int age() default 1; //有默认值
}
复制代码
使用
public class MainActivity extends AppCompatActivity {
// @BindView("amszlk")
@BindView(value = "amszlk",age=10)
private String name;
}
复制代码
使用注解时,如果定义的注解中的 类型元素 无默认值,则必须传值
5、注解应用场景
按照@Retention元注解 定义的注解存储方式,注解可以被在三种场景下使用
5.1 SOURCE:作用于源码级别的注解,可以提供给IDE语法检查,APT等场景使用
5.1.1、IDE语法检查
在Android开发中,在support-annotation和androidx.annotation中均提供了@IntDef注解,此注解的定义如下
@Retention(SOURCE)//源码级注解
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the allowed constants for this element */
int[] value() default {};
/** Defines whether the constants can be used as a flag, or just as an enum (the default) */
boolean flag() default false;
/**
* Whether any other values are allowed. Normally this is
* not the case, but this allows you to specify a set of
* expected constants, which helps code completion in the IDE
* and documentation generation and so on, but without
* flagging compilation warnings if other values are specified.
*/
boolean open() default false;
}
复制代码
Java中的枚举的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中,比常量多5到10倍的内存占用,该注解的意义在于能够取代枚举,实现方法入参限制
例:
我们定义一个方法test,此方法接收参数coder需要在,Java和JavaScript中选一个
public enum Coder {
JAVA,JAVASCRIPT
}
public void test(Coder coder){
}
复制代码
我们现在为了进行内存优化,不再使用枚举
public class Test {
public static final int JAVA=1;
public static final int JAVASCRIPT=2;
public void test(int coder){
}
}
复制代码
然而此时,调用test方法,由于用的是test,无法进行类型限定,可以传1和2之外的数,这是我们可以用@IntDef增加自定义注解
@IntDef(value={JAVA,JAVASCRIPT})//限定为JAVA和JAVASCRIPT
@Target(ElementType.PARAMETER)//作用于参数
@Retention(RetentionPolicy.SOURCE)//源码级别的注解
public @interface Coder {
}
public void test(@Coder int coder){
}
复制代码
这样调用的时候传递的参数如果不是JAVA和JAVASCRIPT就会报错
5.1.2APT注解处理器
APT:Annotation Processor Tools 注解处理器,用于处理注解,编写好的Java源文件,需要经过javac编译,翻译为虚拟机能够加载解析的字节码文件Class文件,注解处理器是javac自带的一个工具,用来编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器,注册的注解处理器由javac调起,并将注解信息传递给注解处理器进行处理
注解处理器是对注解应用最广泛的场景。Glide,ARouter,ButterKnifer,Tinker,EventBus等框架中都有注解处理器的影子,这些框架中对注解定义并不是Source级别,更多的是Class级别,因为Class级别包含了Source级别
5.2CLASS
定义为CLASS的注解,会保留在class文件中,但是会被虚拟机忽略,即无法在运行期反射获取注解。此时完全符合此种注解的应用场景为字节码操作,如热修复Roubust中
字节码操作:直接修改字节码class文件达到修改代码执行逻辑的目的
举个栗子:登录拦截,在我们程序中有多处需要进行是否登录的判断,判断是否登录,是通过验证,未登录就进入登录,如果使用普通的编码方式,就是在许多方法里加上if else判断,但是有了CLASS时期的注解,我们可以使用AOP,将程序中所有功能点划分为需要登录与不需要登录两种切面,用注解来区分切面
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login{
}
@Login
public void jumpAActivity(){
}
public void jumpBActivity(){
}
复制代码
在以上代码中,我们能够在该类所在编译的字节码中获取得注解为Login的方法,然后去操作字节码,修改class中的内容,加入if else
//Class文件
@Login
public void jumpAActivity(){
if(this.isLogin) {
this.startActivity(new Intent(this,LogingActivity.class))
} else {
this.startActivity(new Intent(this,AActivity.class))
}
}
复制代码
5.3 RUNTIME
注解保留至运行期,意味着我们能够在运行期间结合反射技术来获取注解中的所有信息
反射与动态代理点我查看
6.总结
这篇文章从注解的定义,使用和使用场景把注解学习了一遍,以后也会以别的技术结合起来再学习的,学技术不能只为了学而学,我们要把他们落地,实践起来!一起加油!希望大佬们一键三连!