We often see things like "@Override", "@Target" and so on in java code. What are these?
In java they are "annotations".
The following is the explanation of Baidu Encyclopedia: java.lang.annotation.Retention can instruct the compiler to treat your custom Annotation when you define the Annotation pattern. The compiler will leave Annotation information in the class archive, but not read by the virtual machine, but only provide information when the compiler or tool program runs.
In other words, annotations are based on class files, and have the same effect as macros in C language.
There is no trace of annotation in the class file.
The basis of annotation is reflection. Therefore, the annotation can be understood as a concept unique to Java.
1. Meta Note
In the java.lang.annotation package, four "primitives" of annotation have been defined.
1).@Target, used to clarify the type being modified: (methods, fields, classes, interfaces, etc.)
2).@Retention, describes the existence of annentation:
The RetentionPolicy.RUNTIME annotation will exist in the class bytecode file and can be obtained through reflection at runtime.
RetentionPolicy.CLASS The default retention policy, the annotation will exist in the class bytecode file, but it cannot be obtained during runtime.
The RetentionPolicy.SOURCE annotation exists only in the source code and does not contain it in the class bytecode file.
3).@Documented, by default, annotations will not be recorded in javadoc, but this annotation can be used to indicate that this annotation needs to be recorded.
4).@Inherited Meta-annotation is a tag annotation, and @Inherited explains that a certain annotated type is inherited.
If an annotation type using @Inherited modification is used for a class, the annotation will be used for a subclass of the class.
2. Custom annotations
package com.joyfulmath.jvmexample.annnotaion;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** @author deman.lu* @version on 2016-05-23 13:36*/@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface FruitName {String value() default "";} First of all, an annotation generally requires 2 meta annotation modifications:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
The specific functions have been explained above.
All annotations will have a section similar to "func". This can be understood as an annotation parameter.
package com.joyfulmath.jvmexample.annnotaion;import com.joyfulmath.jvmexample.TraceLog;/*** @author deman.lu* @version on 2016-05-23 13:37*/public class Apple {@FruitName("Apple")String appleName;public void displayAppleName(){TraceLog.i(appleName);}} The log of this code:
05-23 13:39:38.780 26792-26792/com.joyfulmath.jvmexample I/Apple: displayAppleName: null [at (Apple.java:16)]
Why is the assignment not successful? How to assign the file to the annotation "Apple"? How to do it if the compiler doesn't know yet.
3. Annotation processor
We also need a processor to explain how the annotations work, otherwise it will be almost the same as the annotations.
Through reflection, the annotation content can be obtained:
package com.joyfulmath.jvmexample.annnotaion;import com.joyfulmath.jvmexample.TraceLog;import java.lang.reflect.Field;/*** @author deman.lu* @version on 2016-05-23 14:08*/public class FruitInfoUtils {public static void getFruitInfo(Class<?> clazz){String fruitNameStr = "";Field[] fields = clazz.getDeclaredFields();for(Field field:fields){if(field.isAnnotationPresent(FruitName.class)){FruitName fruitName = field.getAnnotation(FruitName.class);fruitNameStr = fruitName.value();TraceLog.i(fruitNameStr);}}}}This is the general usage of annotations.
Android annotation framework analysis
As can be seen from the above, the use of annotation frameworks essentially requires reflection.
But if I use the annotation framework with the function of reflection, then I might as well use it directly, it is simpler.
If there is a mechanism, it can avoid writing a lot of duplicate similar code, especially during android development, a large number of findviewbyid & onClick and other events are corresponding.
The code pattern is consistent, but the code is different. At this time, using the annotation framework can save a lot of development time, and of course, it will increase other overhead accordingly.
Here is an example of using butterknife:
@BindString(R.string.login_error)
String loginErrorMessage;
It seems very simple, it is to assign a string to the initial value corresponding to string res. This way you can save some time. Of course this is just an example.
If you use other annotations heavily, you can save a lot of development time.
Let's see how it is implemented:
package butterknife;import android.support.annotation.StringRes;import java.lang.annotation.Retention;import java.lang.annotation.Target;import static java.lang.annotation.ElementType.FIELD;import static java.lang.annotation.RetentionPolicy.CLASS;/*** Bind a field to the specified string resource ID.* <pre><code>* {@literal @}BindString(R.string.username_error) String usernameErrorText;* </code></pre>*/@Retention(CLASS) @Target(FIELD)public @interface BindString {/** String resource ID to which the field will be bound. */@StringRes int value();} BindString has only one parameter, value, that is, assigning the value to @StringRes.
Same as above, the above is where the annotation is defined and used, but the real explanation of the annotation is as follows: ButterKnifeProcessor
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env)
This function intercepts some code:
// Process each @BindString element.for (Element element : env.getElementsAnnotatedWith(BindString.class)) {if (!SuperficialValidation.validateElement(element)) continue;try {parseResourceString(element, targetClassMap, erasedTargetNames);} catch (Exception e) {logParsingError(element, BindString.class, e);}} Find all the elements of BindString annotations and start analyzing:
private void parseResourceString(Element element, Map<TypeElement, BindingClass> targetClassMap,Set<TypeElement> erasedTargetNames) {boolean hasError = false;TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();// Verify that the target type is String.if (!STRING_TYPE.equals(element.asType().toString())) {error(element, "@%s field type must be 'String'. (%s.%s)",BindString.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName());hasError = true;}// Verify common generated code restrictions.hasError |= isInaccessibleViaGeneratedCode(BindString.class, "fields", element);hasError |= isBindingInWrongPackage(BindString.class, element);if (hasError) {return;}// Assemble information on the field.String name = element.getSimpleName().toString();int id = element.getAnnotation(BindString.class).value();BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);FieldResourceBinding binding = new FieldResourceBinding(id, name, "getString", false);bindingClass.addResource(binding);erasedTargetNames.add(enclosingElement);} First verify whether the element is a string type.
// Assemble information on the field.String name = element.getSimpleName().toString();int id = element.getAnnotation(BindString.class).value();
Get the name of the field, and the string id.
final
Map<TypeElement, BindingClass> targetClassMap
Elements and annotation descriptions are stored one by one in the map.
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {TypeElement typeElement = entry.getKey();BindingClass bindingClass = entry.getValue();try {bindingClass.brewJava().writeTo(filer);} catch (IOException e) {error(typeElement, "Unable to write view binder for type %s: %s", typeElement,e.getMessage());}}return true;} This is where the annotation framework is started, an independent process. This article will not study the specific details, just clear it, this is the framework driven place.
From the above information, all annotation information is stored in the targetClassMap.
The code marked red above should be the core of the annotation framework.
Since Java SE5, Java has introduced apt tool, which can preprocess annotations. Java SE6 supports extended annotation processor.
And process multiple times during compilation. We can use a custom annotation processor to generate new Java code according to the rules when Java is compiled .
JavaFile brewJava() {TypeSpec.Builder result = TypeSpec.classBuilder(generatedClassName).addModifiers(PUBLIC); if (isFinal) {result.addModifiers(Modifier.FINAL);} else {result.addTypeVariable(TypeVariableName.get("T", targetTypeName));}TypeName targetType = isFinal ? targetTypeName : TypeVariableName.get("T"); if (hasParentBinding()) {result.superclass(ParameterizedTypeName.get(parentBinding.generatedClassName, targetType));} else {result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, targetType));} result.addMethod(createBindMethod(targetType)); if (isGeneratingUnbinder()) {result.addType(createUnbinderClass(targetType));} else if (!isFinal) {result.addMethod(createBindToTargetMethod());}return JavaFile.builder(generatedClassName.packageName(), result.build()).addFileComment("Generated code from Butter Knife. Do not modify!").build();} The key to this passage is to create a new file.
Then write the relevant content.