那些年学习注解所踩过的坑

前言

作为一个开发人员,看到@Override都很熟悉吧。很多主流框架也大量运用了注解,那么我们想实现自己的注解应该这么做呢?折腾了一大圈,算是掌握了注解的使用。最后发现对于自定义注解,尤其是对于初学者而言,代码怎么写不是最难的,难的是环境的搭建。

先灌个鸡汤,给你点动力,注解既能解放生产力,也能装装X。

本文目的
  1. 了解注解,入门。
  2. 在Android Studio上的使用。

注解是什么鬼?
java中Annotation是代码里的特殊标记,这些标记在编译,类加载,运行时被读取,并作出相应的处理。

自定义Annotation

1. 定义方法

定义一个Annotation需要使用@interface关键字,Annotation没有方法,只有成员变量,但是成员变量定义的方式和方法的定义很像。

public @interface PrintHello{
//看起来很像方法,但是是成员变量,只是其定义形式很像方法的定义
    int value();
    String what();
}

然后使用方法如下:

@PrintHello(value=0,what="")
public int a ;

2.元注解

仅仅用上面的方法是不够的,还需要用JDK提供的元注解来修饰我们的自定义注解。详细介绍如下图:
这里写图片描述

一个完整注解的定义如下:

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Target({ElementType.FIELD,ElementType.METHOD , ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface PrintHello {
    int value();
    String what();
}

3.注解信息的提取

这一步是重点啦,坑大多在这一步。

上面说了注解有三种保存策略,其中runtime是在运行时通过反射来实现的,说起反射,大家心里第一个念头肯定是影响效率。但是这并不能阻止我们使用强大的注解,因为还有编译时注解,将保存策略设置为source或者class级别的。

首先,我们创建一个提取类,这个类是继承自AbstractProcessor的。

代码如下:

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;


/*
这两个注解分别对应我注释掉的getSupportedAnnotationTypes()和getSupportedSourceVersion,在java7以上可以用注解代替掉这两个方法。
*/
//注意:必须写全路径 com.wl.annotation.PrintHello

@SupportedAnnotationTypes({"com.wl.annotation.PrintHello"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ZeusAnnotation extends AbstractProcessor {

   /*    支持的注解,可以使用通配符
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new HashSet<String>();
        set.add("com.wl.annotation.PrintHello");
        return set;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_7;
    }
    */

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        File file = new File("D:\\javaFile\\test");
        if(!file.exists())
            file.mkdirs();  
        return true;
    } 
}

这段代码也没什么好说的,process()方法就是我们需要处理逻辑的地方,这里我创建了一个文件,通过是否创建了文件来判断注解是否运行了,因为输出语句是在编译器,在Eclipse中不知道怎么查看编译信息,Android Studio中就可以直接查看编译信息。

==向javac注册注解处理器==

当我们的注解处理器写好以后怎么用呢,要将它注册到javac中,这样编译的时候才找得到。

注意,是Eclipse环境,下面会详细介绍AS环境

在工程中创建与src同级的目录META-INF,然后在META-INF中创建文件夹services,然后在services文件夹中创建文件,文件名为javax.annotation.processing.Processor,然后编辑该文件,里面写上你的注解处理器的路径(含包名),比如我的是com.wl.annotation.ZeusAnnotation

看图:
这里写图片描述

将该工程导出为jar包,只勾选src和META-INF两个文件夹。
这里写图片描述

接着创建一个测试工程,将上一步导出的jar包拷贝到libs文件夹中并添加到依赖工程中,接着在代码中使用注解。

public class Student implements Serializable{

    @PrintHello(value = 0, what = "")
    public int stuId;
    public String stuName;
    public boolean stuSex;
    public int stuAge;
    @PrintHello(value = 0, what = "")
    public void setId(int id){
        this.stuId = id;
    }
    @PrintHello(value = 0, what = "")
    public Student(int stuId , String stuName , boolean stuSex , int stuAge){
        this.stuId = stuId;
        this.stuName = stuName;
        this.stuSex = stuSex;
        this.stuAge = stuAge;
    }
    @PrintHello(value = 0, what = "")
    public Student() {
        // TODO Auto-generated constructor stub
    }

}

到这里你可能会问了,怎么没看到你向javac注册啊?别急,下面就是了。

工程右键 → properties → Java Compile
这里写图片描述

这里写图片描述

到这里就可以了,run一下我们的工程,可以看到D盘下生成了测试文件。

接下来就是高能时刻了,在AS上使用自定义注解,那可真是一把辛酸泪啊

古努尔哈赤有七大恨,今有注解几大坑啊,浪费了我一大堆时间。

  1. 合适的资料少,百度google了一大圈也没几个真正适合的。
  2. 因为在Eclipse中将jar包添加到factory path的时候只能添加jar包,所以有的博客就说注解只能用jar包,然后在AS下打包,关键在AS下还要用maven,对于android开发者来说真是一口老血吐出来了,三丈远的那种。
  3. 版本问题,可能以前Gradle不支持APT(Annotation Processor Tool),很多博客都提到了要用apt插件,也有人说Gradle2.2以后支持apt了,但是据我测试,我2.1.0的gradle是支持apt的。
  4. 就是关于META-INF文件夹的,我一开始是先从AS开始上手注解的,然后有的博客自己手动创建该目录,有的用auto-service插件,有的用maven打包,有的又不用,有的用apt插件,有的说不用,你可知道我的心情。然后我就先去从Eclipse入手,搞清楚了再来从AS搞起。

重点了,建议认真阅读,不要跳过
1. 在AS中可以以添加依赖的方法使用注解,不一定非要用jar包。
2. auto-service插件是自动帮助我们创建META-INF文件夹及里面的文件的,如果我们自己写就不需要用该插件。同时,不用即用这个插件又自己创建,否则会造成文件冲突。
3. 在AS中,要创建和java同级目录resources,里面创建文件夹META-INF,里面创建文件夹services,里面创建文件javax.annotation.processing.Processor
4. Gradle2.1.0以上是支持apt的,只是不是用apt而是用compile或者provided。
5. compile和provided的区别是前者添加的会被打包进apk,而后者添加的只是在编译器用,不会被打包进apk。

先看看工程结构:

其中:annotation里面是定义的注解,annotation-compile是注解提取类。
这里写图片描述

然后annotation-compile的build.gradle中添加annotation依赖。

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile project(':annotation')
}

然后在app的build.gradle中添加依赖:

compile project(':annotation')
    provided project(':annotation-compile')

好了,编译运行,在Gradle Console可以看到编译过程中输出了hello信息。同时反编译我们apk,可以看到源码里面没有提取器的代码。

后记

到这里终于大功告成了,走了很多弯路,但是最后掌握的感觉也很棒。在这个过程中看到很多博客,从前人那里学了很多同时也发现一些错误,在此希望这篇博客能让大家少踩一点坑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值