Overview of Java Annotations and Annotation Processors

meta-annotation

  • Whether the @Documented annotation will be included in the JavaDoc
  • @Inherited Whether subclasses of the annotated class will be annotated
  • @Retention annotation retention location
  • Scope of the @Target annotation

The value range of the @Retention annotation value attribute is the three enumeration values ​​of the enumeration class RetentionPolicy, SOURCE, CLASS and RUNTIME. The reserved locations of the corresponding annotations are .java source code, .class code and bytecode.

SOURCE type annotations are only reserved in .java code, and are generally used as hints for development specifications. For example @Override, @SuppressWarnings, etc.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

The CLASS type annotations are reserved in the .class file, and are generally processed with the processor. This will be emphasized below, and will be skipped for the time being.

@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
public @interface NotNull {
}

The annotations of the RUNTIME type will be retained in the bytecode, which is generally used as a mark in the bytecode and is called by reflection.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}

@Target can take the following values

  • ElementType.TYPE: used to describe class, interface or enum declarations
  • ElementType.FIELD: used to describe instance variables
  • ElementType.METHOD
  • ElementType.PARAMETER
  • ElementType.CONSTRUCTOR
  • ElementType.LOCAL_VARIABLE
  • ElementType.ANNOTATION_TYPE another annotation
  • ElementType.PACKAGE is used to record package information of java files

Java8 new

In Java8, annotations can be written anywhere, which is relatively flexible. The program element types of the two new annotations are ElementType.TYPE_USE and ElementType.TYPE_PARAMETER. Added @Repeatable annotation. Before Java8, if an attribute can have multiple values, an array will be defined. After Java8, you can use unified annotations for the same attribute multiple times, which increases readability. To be honest, these changes don't make much sense to me. If you are interested, please check it out for yourself.

Annotations for SOURCE and CLASS types

See Annotation Processors below

Annotation of the RUNTIME type

The annotations of the RUNTIME type are relatively simple, and are generally obtained by reflection. The general routine is: first write the required annotations, then add a line of "initialization/registration" code to the annotated class, get the annotated properties inside the code, and then reflect the corresponding methods or process some logic by yourself. The more typical ones are the annotation module of Android xUtils3 source code analysis, the use of Android EventBus3.0 and source code analysis . Since a large number of reflections consume some performance, some frameworks such as ButterKnife and ARouter use CLASS+AnnotationProcessor type annotations. However, with the improvement of device performance, the performance of reflection consumption becomes less and less obvious. The annotation routine of the RUNTIME type is relatively simple. Comparing the code of xUtils3 and EventBus3, I believe readers can understand it quickly.

Annotation Processor

Annotation processor is a tool of Javac that scans and processes annotations at compile time, and can do whatever you want in the AbstractProcessor#process callback, usually used to generate .java auxiliary code. The annotations of the three types of custom (SOURCE, CLASS, RUNTIME) can be processed by the annotation processor. There seem to be a few odd patterns in using annotations now:

  1. SOURCE is almost useless
  2. CLASS+AnnotationProcessor
  3. RUNTIME+reflection

In fact, there is no limitation of this mode. SOURCE, CLASS, and RUNTIME can all be processed by the annotation processor. Due to the limitation of the reserved location, reflection is generally matched with RUNTIME. The author tried to change the Route annotation (CLASS+AnnotationProcessor mode) in ARouter to the SOURCE type, and the project still runs. To sum up, I personally think that SOURCE+AnnotationProcessor is the best annotation mode.

The registration of the annotation processor can be 'com.google.auto.service:auto-service:1.0-rc2'implemented by, aotu-service is actually an annotation, the function is to provide @AutoService annotation and AutoServiceProcessor to assist in generating the annotation processor registration file. Of course, you can also manually create a resources/META-INF/services folder in the main directory, and then create a new javax.annotation.processing.Processor file in the above folder, and finally write the full path of the annotation processor in the above file. .

Regarding the auxiliary generation of .java code, I only know that both ARouter and ButterKnife are used api 'com.squareup:javapoet:1.7.0'. The javapoet framework provides many APIs to assist in generating .java files, properties, methods, class names, and more. Another relatively primitive way is to create the file yourself, and then manually write the strings one by one.

The main function of annotations is to mark and carry attributes. The above can get the mark and also help to generate .java code, but these .java codes are not instantiated, which is equivalent to just writing .java files without any calls. ARouter's approach is to obtain all classes in all dex files, then extract the required classes according to the specified rules, and finally obtain the instance by reflection and call the instance method. The approximate code is as follows

    public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
        final Set<String> classNames = new HashSet<>();
        // 所有dex文件路径
        List<String> paths = getSourcePaths(context);
        final CountDownLatch parserCtl = new CountDownLatch(paths.size());
        for (final String path : paths) {
            DefaultPoolExecutor.getInstance().execute(new Runnable() {
                @Override
                public void run() {
                    DexFile dexfile = null;
                    try {
                        if (path.endsWith(EXTRACTED_SUFFIX)) {
                            //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                            dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                        } else {
                            dexfile = new DexFile(path);
                        }
                        Enumeration<String> dexEntries = dexfile.entries();
                        while (dexEntries.hasMoreElements()) {
                            String className = dexEntries.nextElement();
                            if (className.startsWith(packageName)) {
                                classNames.add(className);
                            }
                        }
                    } catch (Throwable ignore) {
                        Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                    } finally {
                        ...
                        parserCtl.countDown();
                    }
                }
            });
        }
        parserCtl.await();
        Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
        return classNames;
    }

routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
for (String className : routerMap) {
    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
        // This one of root elements, load root.
        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance()))
                            .loadInto(Warehouse.groupsIndex);
     } 
}

ButterKnife does the following

  ButterKnife.bind(this);

  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

  private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    try {
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      ...
    }
  }

  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    try {
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
    } catch (ClassNotFoundException e) {
      ...
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

Find the class generated by AnnotationProcessor according to the annotation, call the instance of the generated class through reflection, and call findViewById, SetOnClickListener, etc. in the constructor of the generated class.

write at the end

The meaning of annotation is the use of API. The difficulty of writing is not the annotation itself. If you are going to use RUNTIME+ reflection, you need some knowledge of reflection. If you are using Processor, you need to understand some of the syntax of javapoet (I believe readers are reluctant to write strings in the most primitive way). Either way, it's not too difficult, it just requires a little extra knowledge. This article outlines the process of using annotations and annotation processors. If you want to have a deeper understanding of this aspect, it is recommended to look at the annotated frameworks such as EventBus3, ButterKnife, xUtils3, and ARouter. It is best to write a small demo exercise by yourself. Use is the ultimate goal~

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325568787&siteId=291194637