本文将从以下几点为你介绍 java 注解以及如何自定义

  • 引言
  • 注解定义
  • 注解意义
  • 注解分类
  • 自定义
  • 结束语

引言

Java 注解在日常开发中经常遇到,但通常我们只是用它,难道你不会好奇注解是怎么实现的吗?为什么 @Data 的注解可以生成 getter 和 setter 呢?为什么 @BindView 可以做到不需要 findViewById 呢?为什么 retrofit2 只要写个接口就可以做网络请求呢?本文将为你一一解答其中的奥妙。另外注解依赖于反射,我相信绝大多数的 Java 开发者都写过反射,也都知道反射是咋回事,所以如果你还不理解反射,请先花几分钟熟悉后再阅读本文。

注解定义

Annotation 也叫元数据,是代码层面的说明。它在 JDK1.5 以后被引入,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。我们可以简单的理解注解只是一种语法标注,它是一种约束、标记。

注解意义

在 JDK1.5 以前,描述元数据都是使用 xml 的,但是 xml 总是表现出松散的关联度,导致文件过多时难以维护,比如 Spring 早期的注入 xml。因此 Java1.5 引入注解最大的原因就是想把元数据与代码强关联,比如 Spring 2.0 大多转为注解注入。 虽然注解做到了强关联,但是一些常量参数使用 xml 会显结构更加清晰,所以在日常使用时,总是会把 xml 和 Annotation 结合起来使用以达到最优使用。

注解分类

通常我们会按照注解的运行机制将其分类,但是在按照注解的运行机制分类之前,我们先按基本分类来看一遍注解。

基本分类

  • 内置注解(基本注解) 内置注解只有三个,位于 java.lang 包下,他们分别是
    • @Override - 检查该方法是否是重载方法,如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
    • @Deprecated - 标记过时方法,如果使用该方法,会报编译警告。
    • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
  • 元注解 元注解只有四个,位于 java.lang.annotation 包中
    • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入 class 文件中,或者是在运行时可以通过反射访问
    • @Documented - 标记这些注解是否包含在用户文档中
    • @Target - 标记这个注解应该是哪种 Java 成员
      • ElementType.CONSTRUCTOR 构造方法声明
      • ElementType.FIELD 字段声明
      • ElementType.LOCAL_VARIABLE 局部变量声明
      • ElementType.METHOD 方法声明
      • ElementType.PACKAGE 包声明
      • ElementType.PARAMETER 参数声明
      • ElementType.TYPE 类、接口方法
    • @Inherited - 标记注解可继承
  • 自定义注解 可以通过元注解来自定义

运行机制分类

主要是按照元注解中的 @Retention 参数将其分为三类

  • 源码注解 (@Retention(RetentionPolicy.SOURCE)) 注解只在源码中存在,编译时丢弃,编译成.class 文件就不存在了
  • 编译时注解 (@Retention(RetentionPolicy.CLASS)) 编译时会记录到.class 中,运行时忽略
  • 运行时注解 (@Retention(RetentionPolicy.RUNTIME)) 运行时存在起作用,影响运行逻辑,可以通过反射读取

我们可以简单的把 Java 程序从源文件创建到程序运行的过程看作为两大步骤

  • 源文件由编译器编译成字节码
  • 字节码由 java 虚拟机解释运行

那么被标记为 RetentionPolicy.SOURCE 的注解只能保留在源码级别,即最多只能在源码中对其操作,被标记为 RetentionPolicy.CLASS 的被保留到字节码,所以最多只到字节码级别操作,那么对应的 RetentionPolicy.RUNTIME 可以在运行时操作。

自定义

前面的都是司空见惯的知识点,可能大家都知道或有有了解过,但是一说到自定义,估计够呛,那么下面就按运行机制的分类,每一类都自定义一个注解看下注解到底是怎么回事。

RetentionPolicy.RUNTIME

运行时注解通常需要先通过类的实例反射拿到类的属性、方法等,然后再遍历属性、方法获取位于其上方的注解,然后就可以做相应的操作了。 比如现在有一个 Person 的接口以及其实现类 Student。

public interface Person {
    @PrintContent("来自注解 PrintContent 唱歌")
    void sing(String value);
@PrintContent(<span class="hljs-string">"来自注解 PrintContent 跑步"</span>)
void run(String value);

@PrintContent(<span class="hljs-string">"来自注解 PrintContent 吃饭"</span>)
void eat(String value);

@PrintContent(<span class="hljs-string">"来自注解 PrintContent 工作"</span>)
void work(String value);

}

复制代码

实现类

public class Student implements Person {
    @Override
    public void sing(String value) {
        System.out.println(value == null ? "这是音乐课,我们在唱歌" : value);
    }
@Override
public void run(String value) {
    System.out.println(value == null ? <span class="hljs-string">"这是体育课,我们在跑步"</span> : value);
}

@Override
public void eat(String value) {
    System.out.println(value == null ? <span class="hljs-string">"中午我们在食堂吃饭"</span> : value);
}

@Override
public void work(String value) {
    System.out.println(value == null ? <span class="hljs-string">"我们的工作是学习"</span> : value);
}

}

复制代码

执行逻辑

   @Autowired
    private Person person;
public void <span class="hljs-function"><span class="hljs-title">student</span></span>() {
    person.eat(null);
    person.run(null);
    person.sing(null);
    person.work(null);
}
复制代码

我们需要实现的点

  1. 自定义属性注解 @Autowired 表示自动注入 Student 对象给 person。
  2. 提供方法注解 @PrintContent 表示当 value 为 null 时打印的默认值,并在调用的方法前后插入自己想做的操作。

因为这是一个运行时的注解,所以我们需要反射先拿到这个注解,然后再对其进行操作。 Autowired 的实现,可以看到非常简单,仅仅是先拿到类的所有属性,然后对其遍历,发现属性使用了 Autowired 注解并且是 Person 类型,那么就 new 一个 Student 为其赋值,这样就做到了自动注入的效果。

    private static void inject(Object obj) {
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            if (field.getType() == Person.class) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    try {
                        Person student = new Student();
                        field.set(obj, student);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }}}
        }}
复制代码

如果是想当 value 为 null 时打印的注解的默认值,并在调用的方法前后插入自己想做的操作。这种对接口方法进行拦截并操作的称为动态代理,java 提供 Proxy.newProxyInstance 支持。

   private static void inject(Object obj) {
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            if (field.getType() == Person.class) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    try {
                        Person student = new Student();
                        Class<?> cls = student.getClass();
                        Person person = (Person) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), new DynamicSubject(student));
                        field.set(obj, person);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }}}
        }}
复制代码

其中需要自定义 DynamicSubject,即对方法的真实拦截操作。这样就会把 @PrintContent 注解的值作为参数打印处理。

   @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.isAnnotationPresent(PrintContent.class)) {
            PrintContent printContent = method.getAnnotation(PrintContent.class);
            System.out.println(String.format("----- 调用 %s 之前 -----", method.getName()));
            method.invoke(object, printContent.value());
            System.out.println(String.format("----- 调用 %s 之后 -----\n", method.getName()));
            return proxy;
        }
        return null;
    }
复制代码

最后附上两个自定义的注解

@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
}

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PrintContent {
String value();
}

复制代码

以上可以帮助你对 retorfit2 的理解,因为动态代理就是它的核心之一。如果你想学习更多,可以参考仿 retorfit2 设计实现的 Activity 参数注入

RetentionPolicy.SOURCE

大家或许对 @BindView 生成代码有所怀疑,对 @Data 如何生成代码有所好奇,那么我们就自定义个 @Data 来看看怎么注解是怎么生成代码的。本文依赖于 idea 工具,并非 google 提供的 @AutoService 实现。 bean 是开发中经常被使用的,getter、setter 方法是被我们所厌弃写的,idea 帮我们做了一键生成的插件工具,但是如果这一步都都不想操作呢?我只想用一个注解生成,比如下面的 User 类。

@Data
public class User {
    private Integer age;
private Boolean sex;

private String address;

private String name;

}

复制代码

通过 @Data 就可以生成这样的类,是不是很神奇?

public class User{
    private Integer age;
    private Boolean sex;
    private String address;
    private String name;
public <span class="hljs-function"><span class="hljs-title">User</span></span>() {
}

public Integer <span class="hljs-function"><span class="hljs-title">getAge</span></span>() {
    <span class="hljs-built_in">return</span> this.age;
}

public void <span class="hljs-built_in">set</span>Age(Integer age) {
    this.age = age;
}

public Boolean <span class="hljs-function"><span class="hljs-title">hasSex</span></span>() {
    <span class="hljs-built_in">return</span> this.sex;
}

public void isSex(Boolean sex) {
    this.sex = sex;
}

public String <span class="hljs-function"><span class="hljs-title">getAddress</span></span>() {
    <span class="hljs-built_in">return</span> this.address;
}

public void <span class="hljs-built_in">set</span>Address(String address) {
    this.address = address;
}

public String <span class="hljs-function"><span class="hljs-title">getName</span></span>() {
    <span class="hljs-built_in">return</span> this.name;
}

public void <span class="hljs-built_in">set</span>Name(String name) {
    this.name = name;
}

}

复制代码

要实现这样的神奇操作,大概的思路是先自定义某个 RetentionPolicy.SOURCE 级别的注解,然后实现一个注解处理器并设置 SupportedAnnotationTypes 为当前的注解,最后在 META-INF 注册该注解处理器。所以首先我们定义 Data 注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface Data {
}
复制代码

然后需要自定义注解处理器来处理 Data 注解

@SupportedAnnotationTypes("com.data.Data")
public class DataProcessor extends AbstractProcessor {
}
复制代码

然后在 resources/META-INF 文件夹下新建 services 文件夹,如果没有 META-INF 也新建。然后在 services 文件夹里新建 javax.annotation.processing.Processor 文件,注意名字是固定的,打开文件后写上前面定义的注解处理器全称,比如 com.data.DataProcessor,这样就表示该注解处理器被注册了。 待程序要执行的时候,编译器会先读取这里的文件然后扫描整个工程,如果工程中有使用已注册的注解处理器中的 SupportedAnnotationTypes 里的注解,那么就会执行对应的注解处理中的 process 方法。下面重点处理注解处理器 AbstractProcessor,把大部分的解释都写在注解里。

@SupportedAnnotationTypes("com.data.Data")
public class DataProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "----- 开始自动生成源代码");
        try {
            // 返回被注释的节点
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Data.class);
            for (Element e : elements) {
                // 如果注释在类上
                if (e.getKind() == ElementKind.CLASS && e instanceof TypeElement) {
                    TypeElement element = (TypeElement) e;
                    // 类的全限定名
                    String classAllName = element.getQualifiedName().toString() + "New";
                    // 返回类内的所有节点
                    List<? extends Element> enclosedElements = element.getEnclosedElements();
                    // 保存字段的集合
                    Map<Name, TypeMirror> fieldMap = new HashMap<>();
                    for (Element ele : enclosedElements) {
                        if (ele.getKind() == ElementKind.FIELD) {
                            //字段的类型
                            TypeMirror typeMirror = ele.asType();
                            //字段的名称
                            Name simpleName = ele.getSimpleName();
                            fieldMap.put(simpleName, typeMirror);
                        }
                    }
                    // 生成一个Java源文件
                    String targetClassName = classAllName;
                    if (classAllName.contains(".")) {
                        targetClassName = classAllName.substring(classAllName.lastIndexOf(".") + 1);
                    }
                    JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(targetClassName);
                    // 写入代码
                    createSourceFile(classAllName, fieldMap, sourceFile.openWriter());
                } else {
                    return false;
                }
            }
        } catch (IOException e) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
        }
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "----- 完成自动生成源代码");
        return true;
    }

/**
* 属性首字母大写
*/
private String humpString(String name) {
String result = name;
if (name.length() == 1) {
result = name.toUpperCase();
}
if (name.length() > 1) {
result = name.substring(0, 1).toUpperCase()+ name.substring(1);
}
return result;
}

private void createSourceFile(String className, Map&lt;Name, TypeMirror&gt; fieldMap, Writer writer) throws IOException {
    // 检查属性是否以 <span class="hljs-string">"get"</span>, <span class="hljs-string">"set"</span>, <span class="hljs-string">"is"</span>, <span class="hljs-string">"has"</span> 这样的关键字开头,如果有这样的 属性就报错
    String[] errorPrefixes = {<span class="hljs-string">"get"</span>, <span class="hljs-string">"set"</span>, <span class="hljs-string">"is"</span>, <span class="hljs-string">"has"</span>};
    <span class="hljs-keyword">for</span> (Map.Entry&lt;Name, TypeMirror&gt; map : fieldMap.entrySet()) {
        String name = map.getKey().toString();
        <span class="hljs-keyword">for</span> (String prefix : errorPrefixes) {
            <span class="hljs-keyword">if</span> (name.startsWith(prefix)) {
                throw new RuntimeException(<span class="hljs-string">"Properties do not begin with 'get'、'set'、'is'、'has' in "</span> + name);
            }
        }
    }
    String packageName;
    String targetClassName = className;
    <span class="hljs-keyword">if</span> (className.contains(<span class="hljs-string">"."</span>)) {
        packageName = className.substring(0, className.lastIndexOf(<span class="hljs-string">"."</span>));
        targetClassName = className.substring(className.lastIndexOf(<span class="hljs-string">"."</span>) + 1);
    } <span class="hljs-keyword">else</span> {
        packageName = <span class="hljs-string">""</span>;
    }
    // 生成源代码
    JavaWriter jw = new JavaWriter(writer);
    jw.emitPackage(packageName);
    jw.beginType(targetClassName, <span class="hljs-string">"class"</span>, EnumSet.of(Modifier.PUBLIC));
    jw.emitEmptyLine();
    <span class="hljs-keyword">for</span> (Map.Entry&lt;Name, TypeMirror&gt; map : fieldMap.entrySet()) {
        String name = map.getKey().toString();
        String <span class="hljs-built_in">type</span> = map.getValue().toString();
        //字段
        jw.emitField(<span class="hljs-built_in">type</span>, name, EnumSet.of(Modifier.PRIVATE));
        jw.emitEmptyLine();
    }
    <span class="hljs-keyword">for</span> (Map.Entry&lt;Name, TypeMirror&gt; map : fieldMap.entrySet()) {
        String name = map.getKey().toString();
        String <span class="hljs-built_in">type</span> = map.getValue().toString();
        String prefixGet = <span class="hljs-string">"get"</span>;
        String prefixSet = <span class="hljs-string">"set"</span>;
        <span class="hljs-keyword">if</span> (type.equals(<span class="hljs-string">"java.lang.Boolean"</span>)) {
            prefixGet = <span class="hljs-string">"has"</span>;
            prefixSet = <span class="hljs-string">"is"</span>;
        }
        //getter
        jw.beginMethod(<span class="hljs-built_in">type</span>, prefixGet + humpString(name), EnumSet.of(Modifier.PUBLIC))
                .emitStatement(<span class="hljs-string">"return "</span> + name)
                .endMethod();
        jw.emitEmptyLine();
        //setter
        jw.beginMethod(<span class="hljs-string">"void"</span>, prefixSet + humpString(name), EnumSet.of(Modifier.PUBLIC), <span class="hljs-built_in">type</span>, name)
                .emitStatement(<span class="hljs-string">"this."</span> + name + <span class="hljs-string">" = "</span> + name)
                .endMethod();
        jw.emitEmptyLine();
    }
    jw.endType().close();
}

@Override
public SourceVersion <span class="hljs-function"><span class="hljs-title">getSupportedSourceVersion</span></span>() {
    <span class="hljs-built_in">return</span> SourceVersion.latestSupported();
}

}

复制代码

至此整个 Data 的注解生成器就写完了,由于我们的 idea 没有对应的插件帮助我们做一些操作,所以生成的类编译生成字节码时会报已存在异常,所以如果你想更进一步,可以考虑写个插件,本文仅限注解,未提供这样的插件。 以上完成后,只要在 data class 头添加 @Data 注解,在编译程序之前,就可以看到 target/classes 中存在了我们生成的代码。至此你应该了解 @BindView 或者 @Data 的原理,或许你也可以尝试自定义一个 ButterKnife 框架。

RetentionPolicy.CLASS

字节码级别的实际上对应用程序员来说没多大作用,因为此类注解一般是在字节码文件上进行操作,我们一般理解整个过程是在.java 编译为.class 后在将要被加载到虚拟机之前。那么很显然 RetentionPolicy.CLASS 类别的注解是直接修改字节码文件的。所以一般用此注解需要底层开发人员的配合,或者当你需要造轮子了可以考虑用一下,不过需要 ASM 的配合来使用的,如果仅仅是开发应用基本用不到。 不过这里还是介绍下它的使用,先造场景:现在有个 People 类,其中有个 size 属性等于 9,观察到上方的类注解是 @Prinln(12),现在想在字节码层面把 size 的 9 替换为 12。

@Prinln(12)
public class People {
    int size = 9;
double phone = 12.0;

Boolean sex;

String name;

}

复制代码

由于我们并不知道字节码文件是怎么写的,所以需要先通过 Show Bytecode 的插件来查看类的字节码是啥样子的

// class version 52.0 (52)
// access flags 0x21
public class com/People {

// compiled from: People.java

@Lcom/ann/Prinln;(value=12) // invisible

// access flags 0x0
I size

// access flags 0x2
private D phone

// access flags 0x2
private Ljava/lang/Boolean; sex

// access flags 0x2
private Ljava/lang/String; name

// access flags 0x1
public <init>()V
L0
LINENUMBER 12 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 14 L1
ALOAD 0
BIPUSH 9
PUTFIELD com/People.size : I
L2
LINENUMBER 16 L2
ALOAD 0
LDC 12.0
PUTFIELD com/People.phone : D
RETURN
L3
LOCALVARIABLE this Lcom/People; L0 L3 0
MAXSTACK = 3
MAXLOCALS = 1
}

复制代码

虽然知道了是这样的,但还是看不懂啊,咋整呢?没关系,我们看 ASMified。ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。而且 ASMified 我们是可以看得懂的,虽然没学过,但是很好理解。比如上面的 People 的 ASMified 就是这样的。

public class PeopleDump implements Opcodes {
public static byte[] dump() throws Exception {

    ClassWriter cw = new ClassWriter(0);
    FieldVisitor fv;
    MethodVisitor mv;
    AnnotationVisitor av0;

    cw.visit(52, ACC_PUBLIC + ACC_SUPER, <span class="hljs-string">"com/People"</span>, null, <span class="hljs-string">"java/lang/Object"</span>, null);

    cw.visitSource(<span class="hljs-string">"People.java"</span>, null);
    {
        av0 = cw.visitAnnotation(<span class="hljs-string">"Lcom/ann/Prinln;"</span>, <span class="hljs-literal">false</span>);
        av0.visit(<span class="hljs-string">"value"</span>, new Integer(12));
        av0.visitEnd();
    }
    {
        fv = cw.visitField(0, <span class="hljs-string">"size"</span>, <span class="hljs-string">"I"</span>, null, null);
        fv.visitEnd();
    }
    {
        fv = cw.visitField(ACC_PRIVATE, <span class="hljs-string">"phone"</span>, <span class="hljs-string">"D"</span>, null, null);
        fv.visitEnd();
    }
    {
        fv = cw.visitField(ACC_PRIVATE, <span class="hljs-string">"sex"</span>, <span class="hljs-string">"Ljava/lang/Boolean;"</span>, null, null);
        fv.visitEnd();
    }
    {
        fv = cw.visitField(ACC_PRIVATE, <span class="hljs-string">"name"</span>, <span class="hljs-string">"Ljava/lang/String;"</span>, null, null);
        fv.visitEnd();
    }
    {
        mv = cw.visitMethod(ACC_PUBLIC, <span class="hljs-string">"&lt;init&gt;"</span>, <span class="hljs-string">"()V"</span>, null, null);
        mv.visitCode();
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitLineNumber(12, l0);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, <span class="hljs-string">"java/lang/Object"</span>, <span class="hljs-string">"&lt;init&gt;"</span>, <span class="hljs-string">"()V"</span>, <span class="hljs-literal">false</span>);
        Label l1 = new Label();
        mv.visitLabel(l1);
        mv.visitLineNumber(14, l1);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitIntInsn(BIPUSH, 9);
        mv.visitFieldInsn(PUTFIELD, <span class="hljs-string">"com/People"</span>, <span class="hljs-string">"size"</span>, <span class="hljs-string">"I"</span>);
        Label l2 = new Label();
        mv.visitLabel(l2);
        mv.visitLineNumber(16, l2);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitLdcInsn(new Double(<span class="hljs-string">"12.0"</span>));
        mv.visitFieldInsn(PUTFIELD, <span class="hljs-string">"com/People"</span>, <span class="hljs-string">"phone"</span>, <span class="hljs-string">"D"</span>);
        mv.visitInsn(RETURN);
        Label l3 = new Label();
        mv.visitLabel(l3);
        mv.visitLocalVariable(<span class="hljs-string">"this"</span>, <span class="hljs-string">"Lcom/People;"</span>, null, l0, l3, 0);
        mv.visitMaxs(3, 1);
        mv.visitEnd();
    }
    cw.visitEnd();

    <span class="hljs-built_in">return</span> cw.toByteArray();
}

}

复制代码

这样的话,我们大概可以知道如果要改 size=12,就只要把 mv.visitIntInsn(BIPUSH, 9); 这句话的 9 改为 12 就好了。不过 ASM 在原有的字节码文件中插入或删除或更改,本文未仔细研究。本文是简单粗暴的用新的字节码替换原字节码文件。

public void asm() {
        try {
            ClassReader classReader = new ClassReader(new FileInputStream("target/classes/com/People.class"));
            ClassNode classNode = new ClassNode();
            classReader.accept(classNode, ClassReader.SKIP_DEBUG);
            System.out.println("Class Name:" + classNode.name);
            AnnotationNode anNode = null;
            if (classNode.invisibleAnnotations.size() == 1) {
                anNode = classNode.invisibleAnnotations.get(0);
                System.out.println("Annotation Descriptor :" + anNode.desc);
                System.out.println("Annotation attribute pairs :" + anNode.values);
            }
        File file = new File(<span class="hljs-string">"target/classes/com/People.class"</span>);
        FileOutputStream outputStream = new FileOutputStream(file);
        outputStream.write(copyFromBytecode(anNode == null ? 0 : (int) anNode.values.get(1)));

    } catch (IOException e) {
        e.printStackTrace();
    }

    People people = new People();
    System.out.println(<span class="hljs-string">"people : "</span> + people.size);
}

private byte[] copyFromBytecode(int value) {

    ClassWriter cw = new ClassWriter(0);
    FieldVisitor fv;
    MethodVisitor mv;
    AnnotationVisitor av0;

    cw.visit(52, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, <span class="hljs-string">"com/People"</span>, null, <span class="hljs-string">"java/lang/Object"</span>, null);

    cw.visitSource(<span class="hljs-string">"People.java"</span>, null);

    {
        av0 = cw.visitAnnotation(<span class="hljs-string">"Lcom.ann.Prinln;"</span>, <span class="hljs-literal">false</span>);
        av0.visit(<span class="hljs-string">"value"</span>, new Integer(12));
        av0.visitEnd();
    }
    {
        fv = cw.visitField(0, <span class="hljs-string">"size"</span>, <span class="hljs-string">"I"</span>, null, null);
        fv.visitEnd();
    }
    {
        fv = cw.visitField(0, <span class="hljs-string">"phone"</span>, <span class="hljs-string">"D"</span>, null, null);
        fv.visitEnd();
    }
    {
        fv = cw.visitField(0, <span class="hljs-string">"sex"</span>, <span class="hljs-string">"Ljava/lang/Boolean;"</span>, null, null);
        fv.visitEnd();
    }
    {
        fv = cw.visitField(0, <span class="hljs-string">"name"</span>, <span class="hljs-string">"Ljava/lang/String;"</span>, null, null);
        fv.visitEnd();
    }
    {
        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, <span class="hljs-string">"&lt;init&gt;"</span>, <span class="hljs-string">"()V"</span>, null, null);
        mv.visitCode();
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitLineNumber(10, l0);
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, <span class="hljs-string">"java/lang/Object"</span>, <span class="hljs-string">"&lt;init&gt;"</span>, <span class="hljs-string">"()V"</span>, <span class="hljs-literal">false</span>);
        Label l1 = new Label();
        mv.visitLabel(l1);
        mv.visitLineNumber(12, l1);
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitIntInsn(Opcodes.BIPUSH, value);
        mv.visitFieldInsn(Opcodes.PUTFIELD, <span class="hljs-string">"com/People"</span>, <span class="hljs-string">"size"</span>, <span class="hljs-string">"I"</span>);
        Label l2 = new Label();
        mv.visitLabel(l2);
        mv.visitLineNumber(14, l2);
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitLdcInsn(new Double(<span class="hljs-string">"12.0"</span>));
        mv.visitFieldInsn(Opcodes.PUTFIELD, <span class="hljs-string">"com/People"</span>, <span class="hljs-string">"phone"</span>, <span class="hljs-string">"D"</span>);
        mv.visitInsn(Opcodes.RETURN);
        Label l3 = new Label();
        mv.visitLabel(l3);
        mv.visitLocalVariable(<span class="hljs-string">"this"</span>, <span class="hljs-string">"Lcom/People;"</span>, null, l0, l3, 0);
        mv.visitMaxs(3, 1);
        mv.visitEnd();
    }
    cw.visitEnd();

    <span class="hljs-built_in">return</span> cw.toByteArray();
}
复制代码

实际上 RetentionPolicy.CLASS 的使用,ASM 的配合很重要,所以当你在自己的框架中需要使用这种类型的注解的时候,建议还是学好 ASM 再尝试写此类注解,而不是像我这样全部替换。

结束语

本文注解虽然讲解的多,但是如果你能看完到这里,相信通过了几个例子的描述,你已经知道了几类注解的基本操作过程,已经让你对各种类型的注解有基本的认识,或许看了本文你真的理解了 retrofit2、ButterKnife,甚至可以自己写个简单的 retrofit2 或 ButterKnife 的框架,那就再好不过了。 最后附上源码,感谢阅读。

  • Android

    开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)已开发出 Android,Android 是第一个完整、开放、免费的手机平台。

    293 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...