0.Java 注解的定义

0.注解的使用场景

● 提供信息给编译器:利用注解来来探测错误和警告信息

● 编译阶段时的处理:软件工具利用注解来生成代码、Html文档或其他相应处理;

● 运行时的处理:某些注解可以在程序运行的时候接受代码的提取;

1.注解的作用或者意义

注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义;

Java注解(Annotation) 又称Java标注,是JDK5.0引入的一种的一种注释机制,用于为Java代码提供元数据,

即有关于程序但不属于程序本身的数据(用来描述数据的数据),注解对它们注解的代码的操作没有直接影响;

┌---Deprecated

ElementType---------┐ ├---Documented

1..n | ├---Inherited

<<接口>> <-----Override

Annotation ├---Retention

1 | ├---Target 目标注解

RetentionPolicy-----┘ └---...

2.注解的声明

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 {


  }

3.元注解(注解上面的注解)

在定义注解时, 注解类也能够使用其他的注解声明, 对注解类型进行注解的注解类,我们称之为 meta annotation(元注解)

/ˈmetə/ /ˌænəˈteɪʃn/, 一般的我们在定义自定义注解时, 需要指定的元注解有两个:@Target @Retention, 另外还有

@Documented、@Inherited 和 @Repeatable 5种元注解,@Documented用于被javadoc工具提取成文档, @Inherited表示

继承的意思,允许子类继承父类中定义的注解, @Repeatable 可重复应用的意思,注解的值可同时取多个, 在java1.8引入;

1> @Target(作用域)

限制注解作用域的注解(目标作用域)

注解标记另一个注解,以限制可以应用注解的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");
    }
}

2> @Retention(存储方式)

限制注解存储方式的注解

注解指定标记注解的存储方式

● RetentionPolicy.SOURCE -标记的注解仅保留在源级别中,并被编译器忽略; 编译成class就会被擦除掉 只在源码中

● RetentionPolicy.CLASS -标记的注解在编译时有编译器保留,但Java虚拟机(JVM)会忽略; 保留在class 字节码中

● RetentionPolicy.RUNTIME-标记的注解由JVM保留,因此运行时环境可以使用它; 结合反射技术

@Retention(保留) 三个值中 Source<CLass<Runtime,即Class包含了Source,Runtime包含Source和Class

根据@Retention 元注解定义的注解存储方式,注解可以被在三种场景下使用:

java 参数中带注解_java

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<A> 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);
      }
  }