0.注解的使用场景
● 提供信息给编译器:利用注解来来探测错误和警告信息
● 编译阶段时的处理:软件工具利用注解来生成代码、Html文档或其他相应处理;
● 运行时的处理:某些注解可以在程序运行的时候接受代码的提取;
注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义;
Java注解(Annotation) 又称Java标注,是JDK5.0引入的一种的一种注释机制,用于为Java代码提供元数据,
即有关于程序但不属于程序本身的数据(用来描述数据的数据),注解对它们注解的代码的操作没有直接影响;
┌---Deprecated
ElementType---------┐ ├---Documented
1..n | ├---Inherited
<<接口>> <-----Override
Annotation ├---Retention
1 | ├---Target 目标注解
RetentionPolicy-----┘ └---...
java中所有的注解,默认实现Annotation接口:
- package java.lang.annotation;
- public interface Annotation{
- boolean equals(Object obj);
- int hashCode();
- String toString();
- Class extends Annotation> annotationType();
- }
-
-
- //与声明一个"Class"不同的是,注解的声明使用 @interface 关键字
- public @interface AnnotationTest {
-
-
- }
在定义注解时, 注解类也能够使用其他的注解声明, 对注解类型进行注解的注解类,我们称之为 meta annotation(元注解)
/ˈmetə/ /ˌænəˈteɪʃn/, 一般的我们在定义自定义注解时, 需要指定的元注解有两个:@Target @Retention, 另外还有
@Documented、@Inherited 和 @Repeatable 5种元注解,@Documented用于被javadoc工具提取成文档, @Inherited表示
继承的意思,允许子类继承父类中定义的注解, @Repeatable 可重复应用的意思,注解的值可同时取多个, 在java1.8引入;
限制注解作用域的注解(目标作用域)
注解标记另一个注解,以限制可以应用注解的java元素类型,目标注解指定一下元素之一作为其值:
● ElementType.ANNOTATION_TYPE 可应用于注解类型(annotation_type);
● ElementType.CONSTRUCTOR 可用于构造方法(constructor);
● ElementType.FIELD 可用于字段或属性(field);
● ElementType.LOCAL_VARIABLE 可用于局部变量(variable);
● ElementType.METHOD 可用于方法级注解(method);
● ElementType.PACKAGE 可用于包声明 (package);
● ElementType.PARAMETER 可用于方法中的参数 (parameter);
● ElementType.TYPE 可用于类的 任何元素
示例:
- //@Target(ElementType.TYPE)//只能在类上面标记该注解
- Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})//允许在类与类属性与方法上标记该注解
- @Retention(RetentionPolicy.SOURCE)//注解保留在源码中
- public @interface Test {
- String value(); //无默认值, 注意在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值
- int age() default 1;//有默认值
- }
注解类型元素
在上文元注解中,允许在使用注解时传递参数,我们也能自定义注解的主体 包含 annotation type element
(注解类型元素)声明, 它们看起来很像方法,可以定义可选的默认值; @AnnotationTest(value="帅",age=2)
@Test("帅") String value();//如果成员变量只存在value元素需要传值的情况,否则可省略:@注解名(元素名=值)
@如果不光有value 还有其他元素 如: int id(); String value(); @Test(id=1 value="") 都要写成key-value
- @Test("") //对应 @Target(ElementType.TYPE)
- public class AnnotationTest {
- @Test("")//对应 @Target(ElementType.FIELD)
- int i;
- @Test("")//对应 @Target(ElementType.METHOD)
- public static void main(String[] args) {
- System.out.println("Hello Java");
- }
- }
限制注解存储方式的注解
注解指定标记注解的存储方式
● RetentionPolicy.SOURCE -标记的注解仅保留在源级别中,并被编译器忽略; 编译成class就会被擦除掉 只在源码中
● RetentionPolicy.CLASS -标记的注解在编译时有编译器保留,但Java虚拟机(JVM)会忽略; 保留在class 字节码中
● RetentionPolicy.RUNTIME-标记的注解由JVM保留,因此运行时环境可以使用它; 结合反射技术
@Retention(保留) 三个值中 Source
根据@Retention 元注解定义的注解存储方式,注解可以被在三种场景下使用:

1) Source
Retention.SOURCE,作用于源码级别的注解,可以提供IDE语法检查,APT等场景使用;
在类中使用SOURCE级别的注解,会在其编译后的class中被擦除;
APT注解处理器:
其用于处理注解,编写好Java源文件,需要经过javac的编译,翻译为虚拟机能够加载解析的字节Class文件,注解处理器是
javac自带的一个工具,用来在编译时扫描处理注解信息,你可以为某些注解注册字节的注解处理器,注册的注解处理器由
javac调起,并将注解信息传递给注解处理器进行处理;
注解处理器是对注解应用最广泛的场景,在Glide、EventBus3、ButterKnife、Tinker、ARouter等常用框架中都有
注解处理器的身影,,这些框架中对注解的定义并不是SOURCE级别,更多的是CLASS级别;
- com.xzh.compiler.TestProcessor
- new Module->compiler/java/com.xzh.compiler/MyProcessor ↓
- resources/META-INF/services/javax.annotation.processing.Processor
- app/build.gradle/ annotationProcessor project(':compiler')
-
- //采集到所有的注解信息->Element->注解处理程序 .java->javac->.class
- //APT 注解处理器 Annotation Processor Tools
- @SupportedAnnotationTypes("com.xzh.study.annotation.WeekDay")
- public class TestProcessor extends AbstractProcessor {
- @Override //获取需要处理的注解对象(编译的程序中使用的注解)
- public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
- for (TypeElement typeElement : annotations) {
- Messager messager = processingEnv.getMessager();
- messager.printMessage(Diagnostic.Kind.NOTE,"================>");
- }
- return false;
- }
- }
-
- //IDE语法检查 在Android开发中,support-annotations与androidx.annotation中均有提供@IntDef注解,此注解的定义如下
- @Retention(SOURCE) //源码级别注解
- @Target({ANNOTATION_TYPE})
- public @interface IntDef{
- int[] value() default {};
- boolean flag() default false;
- boolean open() default false;
- }
-
-
Java中Enum(枚举)的实质是单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中,比常量多5到10倍的内存占用,一个对象占用: 12对象头+成员所占内存+8字节对齐;此注解的意义是能够取代枚举,实现如方法入参限制; 枚举是对象;如:我们定义方法setCurrentDay, 此方法接收参数Day 需要在:SUNDAY和MONDAY中选一个,如果使用枚举能够实现为
- public enum WeekDay{
- SUNDAY, MONDAY,TUESDAY,Wednesday,ThursDay,Firday,Saturday
- }
-
-
- public class AnnotationTest {
- static final int SUNDAY = 0;
- static final int MONDAY = 1;
- static final int TUESDAY = 2;
- static final int Wednesday = 3;
- static final int ThursDay = 4;
- static final int Firday = 5;
- static final int Saturday = 6;
- @WeekDay //定义的时候初始化时可检测
- private static int currentIntDay;//在这里检测 比如只能赋值0-6;
- //语法检查 调用setCurrentDay方法由于参数是采用基本数据类型int,
- //将无法进行类型限定,此时使用@IntDef增加自定义注解:
- @IntDef({SUNDAY, MONDAY,TUESDAY,Wednesday,ThursDay,Firday,Saturday})
- @Target({ElementType.PARAMETER, ElementType.FIELD})
- @Retention(RetentionPolicy.SOURCE)
- @interface WeekDay {}
-
-
- public static void setCurrentDay(@WeekDay int currentDay) {
- currentIntDay = currentDay;
- }
-
- public static void main(String[] args) {
- System.out.println("Hello Java");
- setCurrentDay(SUNDAY);
- }
- }
2) CLASS
定义为Class的注解,会保留在class文件中,但会被虚拟机忽略(即无法在运行期反射获取注解),如字节码操作,AspectJ
热修复Robust中应用此场景,所谓的字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的;
示例:是否登录--[已登录(通过验证)|未登录(进入登录)]
如果使用不同的编程方式,需要在代码中进行if-else的判断,若存在10个判断点,需要在每个判断点加入此项判断;此时
我们可以借助AOP(面向切面)编程思想,将程序中所有功能点划分为: 需要登录 和 无需登录两种类型,即两个切面,
对于切面的区分即可采用注解
注解的提取
注解与反射,注解通过该反射获取,首先可以通过Class对象的isAnnotationPresent(Class extends Annotation> cls)
判断是否用了某注解,然后通过 getAnnotation(Class aCls) 获取注解的对象,获取的注解不为null,就可调用属性了;
示例:实现intent传值自动写入对应成员中
- //注解类
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface AutoWrite {
- String value() default "";
- }
-
-
- //该工具主要实现intent传值自动写入对应成员中
- public class InjectUtils {
- public static void injectExtra(Activity activity) {
- //获取总数据
- Intent intent = activity.getIntent();
- Bundle extras = intent.getExtras();
- if (extras == null) return;
- //获取成员
- Class extends Activity> cls = activity.getClass();
- Field[] fields = cls.getDeclaredFields();
- for (Field field : fields) {
- if (field.isAnnotationPresent(AutoWrite.class)) {
- AutoWrite autoWrite = field.getAnnotation(AutoWrite.class);
- if (autoWrite == null) return;
- //获得注解中的key
- String key = autoWrite.value();
- if (extras.containsKey(key)) {
- Object value = extras.get(key);
- //Parcelable数组类型不能直接设置,其他都可以
- //获得单个元素类型
- Class> type = field.getType().getComponentType();
- //获得数组单个元素类型
- if (field.getType().isArray() && Parcelable.class.isAssignableFrom(type)) {
- Object[] array = (Object[]) value;
- if (array != null)
- value = Arrays.copyOf(array, array.length, (Class extends Object[]>) field.getType());
- }
- field.setAccessible(true);//开放访问权限
- try {
- //反射设置属性的值 传实例对象
- field.set(activity, value);
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
-
-
- //进行传值操作
- public class MainActivity extends AppCompatActivity {
- public void test(View view) {
- Intent intent = new Intent(this, TestActivity.class);
- intent.putExtra("name", "NorthStar");
- intent.putExtra("age", 18);
- intent.putExtra("isMen", true);
- startActivity(intent);
- }
- }
-
-
- //使用注解赋值
- public class TestActivity extends AppCompatActivity {
- @AutoWrite(value = "name")
- String name;
-
-
- @AutoWrite(value = "age")
- int age;
-
-
- @AutoWrite(value = "isMen")
- boolean isMen;
-
-
- public static final String TAG = "reflex";
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_test);
- InjectUtils.injectExtra(this);
- initData();
- }
-
-
- public void initData() {
- Log.d(TAG, "name==>" + name + " ,age==>" + age + " ,isMen==>" + isMen);
- }
- }
-