作为程序猿工作这么多年了,技术方面居然没留下什么文章,打算从今天开始我慢慢会写我对一些技术点的理解,后面大概会写一些插件化,热修复,性能优化,Android安全攻防等一些知识点,我希望我写的都是一些工作中常用的,而不是八股文(如果有写得不对的地方,请私信给我)
泛型和反射篇, 请查看 启舰的博客文章 泛型、反射篇 的相关文章
一、注解的声明
1、声明一个注解类型
Java JDK1.5引入了注解,注意注解不能单独存在,单独存在没任何实际的用处,必须与代码相结合才有实际的作用,定义一个注解也非常简单 以@interface
开头 加上你的注解名称 注解就生成了
public @interface InjectTime {
}
Java中的所有注解,默认实现jdk中的Annotation
接口
package java.lang.annotation;
public interface Annotation {
boolean equals(Object var1);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
2、元注解
在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta- annotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有以下两个
@Target
@Retention
另外还有@Documented
与 @Inherited
元注解,前者用于被javadoc工具提取成文档,后者表示允许子类 继承父类中定义的注解。
例如:
@Target(ElementType.PARAMETER) //作用于参数的注解
@Retention(RetentionPolicy.SOURCE) //源码级别注解
public @interface InjectTime {
}
@Target 源码中的定义类型如下
public enum ElementType {
/** 类、接口(包括注解类型)或枚举声明 */
TYPE,
/** 类属性或字段声明(包括枚举常量)*/
FIELD,
/** 方法声明 */
METHOD,
/** 方法的形参声明 */
PARAMETER,
/** 构造函数声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注释类型声明 */
ANNOTATION_TYPE,
/** 包声明 */
PACKAGE,
/**
* 类型参数声明
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 类型的使用
* @since 1.8
*/
TYPE_USE
}
以上其实标识的是你的注解的作用域
我们新建一个类来演示下其中的几种类型,我声明了如下一个注解
@Target({ElementType.PARAMETER,ElementType.CONSTRUCTOR}) //作用于参数和构造函数的注解
@Retention(RetentionPolicy.SOURCE) //源码级别注解
public @interface InjectTime {
}
我声明了@Target
作用域在参数上和类的构造函数,以下可以发现除了这两个地方放的位置没有报错外,其余的都是AS检查报红的(Java AS,IDE工具其实都是用java编写出来的)现在应该明白@Target
的作用了
注解类型元素
像刚才我们使用@Target
注解,我们点击进入源码看看究竟是啥玩意。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
它声明了自己为一个注解类型,注解类型的作用域是你可以加它在你定义的注解之上,但是不能加在其它地方,如果加在你自定义的注解上相当于继承了此注解的功能
ElementType[] value() 这个意思是你可以传递多个值
通过上面你可以发现,注解可以存在一个类似方法的玩意,那这是干啥用的呢?其实就是传递值
声明注解里面一个方法
通过上面我声明了一个value方法,现在放在setSpecies方法参数上开始报错了,那我们怎么让它不报错了,那就是给它加上值了,看到以下我加了一个666的int值就不报错了,
现在还有一个点需要注意,如果你的注解里面方法使用的是value(),你是直接放入值就行了,因为这是系统给你做了处理,如果你不是使用value()方法呢?那你就需要加上key=value格式了,现在我将value更改为mValue,以下可以看到 @InjectTime(mValue=666)
注解改写成这样了,不然会报错
public void setSpecies(@InjectTime(mValue=666) int species) {
this.species = species;
}
@Target({ElementType.PARAMETER,ElementType.CONSTRUCTOR}) //作用于参数和构造函数的注解
@Retention(RetentionPolicy.SOURCE) //源码级别注解
public @interface InjectTime {
int mValue();
// boolean open();
}
声明注解里面两个方法
如果注解里面声明两个方法,那你注解里面的参数每一个都要填写成key=value
的格式,不然会报错,以下是代码说明:
public void setSpecies(@InjectTime(value=666,open=false) int species) {
this.species = species;
}
@Target({ElementType.PARAMETER,ElementType.CONSTRUCTOR}) //作用于参数和构造函数的注解
@Retention(RetentionPolicy.SOURCE) //源码级别注解
public @interface InjectTime {
int value();
boolean open();
}
@Retention
@Retention
进入去看源码,上面已经讲过ElementType.ANNOTATION_TYPE
这是一个注解类型声明,我们可以当成一个注解来加在你自定义的注解之上
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
再看看 RetentionPolicy 的源码说明
public enum RetentionPolicy {
/**
* 标记的注解仅保留在源级别中,并被编译器忽略。
*/
SOURCE,
/**
* 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
*/
CLASS,
/**
* 标记的注解由 JVM 保留,因此运行时环境可以使用它。
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
@Retention 三个值中SOURCE < CLASS < RUNTIME
,即CLASS包含了SOURCE,RUNTIME包含SOURCE、 CLASS。这三个有时候面试的时候面试官会问题,下文会介绍他们不同的应用场景。
二、注解的应用场景
上面讲解了那么多,终于讲解注解究竟有什么用了(顺便说一点枚举比较占用内存,
Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。 比常量多5到10倍的内存占用。
最好应用中少用枚举,如果非必要定义int类型的,就拿Android View 测量里面MeasureSpec来讲,它就为了节约内存 将一个32位的分隔成前两位代表mode 后面30位代表 size)
1、RetentionPolicy.SOURCE
作用于源码级别的注解,仅保留在源级别中,并被编译器忽略
。可提供给IDE语法检查、APT等场景使用
。在类中使用 SOURCE 级别的注解,其编译之后的class中会被丢弃。
IDE工具检查
public static final int HONG_FU_SHI = 1;
public static final int AOZHOU_APPLE = 2;
//Android support-annotations 与 androidx.annotation) 中均有提供 @IntDef 注解,此注解的定义
@IntDef(value = {HONG_FU_SHI,AOZHOU_APPLE})
@Target({ElementType.PARAMETER,ElementType.CONSTRUCTOR}) //作用于参数和构造函数的注解
@Retention(RetentionPolicy.SOURCE) //源码级别注解
public @interface InjectTime {
}
public void setSpecies(@InjectTime int species) {
this.species = species;
}
public void test(){
setSpecies(111);//这个会报红,IED检查不通过
setSpecies(HONG_FU_SHI);
}
Android中的@ColorRes @DrawableRes
等注解都实现了IDE检查
APT注解处理器
APT全称为:“Anotation Processor Tools”,意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文 件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工 具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac 调起,并将注解信息传递给注解处理器进行处理。
RetentionPolicy.SOURCE
级别的注解主要作用其实就是编译期间能够获取注解与注解声明的类中所有成员信息,一般用于生产额外的辅助类,因为这是不被编译器保留的,在你的java class文件中是不存在注解信息的,如果你想验证下可以编译完成后用IDE工具或者反编译查看它的源码内容
2、RetentionPolicy.CLASS
作为class级别的注解,在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略
。
那就是在生成class文件 打包到dex文件中 字节码插桩,因为class文件就是一个16进制的字节码,这字节码你如果要插桩 不能乱改,乱改到时候就报错了,一般借用ASM框架去插桩,美团的 热修复Robus框架就使用到了这种技术方案
也可以实现面向切面编程(AOP)
ASM 可以直接从 jcenter()
仓库中引入,所以我们可以进入https://bintray.com/进行搜索,下面引用一张图说明工作时机
3、RetentionPolicy.RUNTIME
作为runtime级别的注解,由 JVM 保留,因此运行时环境可以使用它。
意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
最后记住:
@Retention 三个值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、 CLASS。
注解指定标记注解的存储方式:
- RetentionPolicy.SOURCE - 标记的注解仅保留在源级别中,并被编译器忽略。
- RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
- RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。
后面我有时间我再记录下 APT注解处理器,字节码插桩 方面的知识。