您的位置:www.85058.com > 技术支持 > Java 注解及其在 Android 中的应用

Java 注解及其在 Android 中的应用

发布时间:2019-12-01 04:56编辑:技术支持浏览(90)

    原标题:Java 注解及其在 Android 中的应用

    一、什么是注解?

    注解对于开发人员来讲既熟悉又陌生,熟悉是因为只要你是做开发,都会用到注解(常见的@Override);陌生是因为即使不使用注解也照常能够进行开发;注解不是必须的,但了解注解有助于我们深入理解某些第三方框架(比如Android Support Annotations、ButterKnife、xUtils、ActiveAndroid等),提高工作效率。

    Java注解又称为标注,是Java从1.5开始支持加入源码的特殊语法元数据;Java中的类、方法、变量、参数、包都可以被注解。这里提到的元数据是描述数据的数据,结合实例来说明:

    <string name="app_name">AnnotationDemo</string>
    

    这里的"app_name"就是描述数据"AnnotionDemo"的数据,这是在配置文件中写的,注解是在源码中写的,如下所示:

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_layout);
        new Thread(new Runnable(){
            @Override
            public void run(){
                setTextInOtherThread();
            }
        }).start();
    }
    

    在上面的代码中,在MainActivity.java中复写了父类Activity.java的onCreate方法,使用到了@Override注解。但即使不加上@Override注解标记代码,程序也能够正常运行。那这里的@Override注解有什么用呢?使用它有什么好处?事实上,@Override是告诉编译器这个方法是一个重写方法,如果父类中不存在该方法,编译器会报错,提示该方法不是父类中的方法。如果不小心拼写错误,将onCreate写成了onCreat,而且没有使用@Override注解,程序依然能够编译通过,但运行结果和期望的大不相同。而如果使用了@Override注解,拼写错误则会得到提示。从示例可以看出,注解有助于阅读代码。

    使用注解很简单,根据注解类的@Target所修饰的对象范围,可以在类、方法、变量、参数、包中使用“@+注解类名+[属性值]”的方式使用注解。比如:

    @UiThread
    private void setTextInOtherThread(@StringRes int resId){
        TextView threadTxtView = (TextView)MainActivity.this.findViewById(R.id.threadTxtViewId);
        threadTxtView.setText(resId);
    }
    

    来源:WngShhng

    style="font-size: 16px;">https://juejin.im/post/5b824b8751882542f105447d

    特别说明:

    • 注解仅仅是元数据,和业务逻辑无关,所以当你查看注解类时,发现里面没有任何逻辑处理;
    • javadoc中的@author、@version、@param、@return、@deprecated、@hide、@throws、@exception、@see是文档注释标记,并不是注解;

    一般的,注解在 Android 中有两种应用方式,一种方式是基于反射的,即在程序的运行期间获取类信息进行反射调用;另一种是使用注解处理,在编译期间生成许多代码,然后在运行期间通过调用这些代码来实现目标功能。

    二、注解的作用

    • 格式检查:告诉编译器信息,比如被@Override标记的方法如果不是父类的某个方法,IDE会报错;

    • 减少配置:运行时动态处理,得到注解信息,实现代替配置文件的功能;

    • 减少重复工作:比如第三方框架xUtils,通过注解@ViewInject减少对findViewById的调用,类似的还有(ButterKnife、ActiveAndroid等);

    在本篇文章中,我们会先重温一下 Java 的注解相关的知识,然后分别介绍一下上面两种方式的实际应用。

    三、注解是如何工作的?

    注解仅仅是元数据,和业务逻辑无关,所以当你查看注解类时,发现里面没有任何逻辑处理,例如XUtils的ViewInject

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ViewInject {
    
        int value();
    
        /* parent view id */
        int parentId() default 0;
    }
    

    如果注解不包含业务逻辑处理,必然有人来实现这些逻辑。注解的逻辑实现是元数据的用户来处理的,注解仅仅提供它定义的属性(类/方法/变量/参数/包)的信息,注解的用户来读取这些信息并实现必要的逻辑。当使用java中的注解时(比如@Override、@Deprecated、@SuppressWarnings)JVM就是用户,它在字节码层面工作。如果是自定义的注解,比如第三方框架ActiveAndroid,它的用户是每个使用注解的类,所有使用注解的类都需要继承Model.java,在Model.java的构造方法中通过反射来获取注解类中的每个属性

    1、Java 注解回顾1. Java 注解的基础知识

    四、注解和配置文件的区别

    通过上面的描述可以发现,其实注解干的很多事情,通过配置文件也可以干,比如为类设置配置属性;但注解和配置文件是有很多区别的,在实际编程过程中,注解和配置文件配合使用在工作效率、低耦合、可拓展性方面才会达到权衡。

    Java 中的注解分成标准注解和元注解。标准注解是 Java 为我们提供的预定义的注解,共有四种: @Override、 @Deprecated 、 @SuppressWarnnings 和 @SafeVarags 。元注解是用来提供给用户自定义注解用的,共有五种(截止到Java8): @Target 、 @Retention 、 @Documented 、 @Inherited 和 @Repeatable ,这里我们重点介绍这五种元注解。

    4.1 、配置文件:

    使用场合:

    • 外部依赖的配置,比如build.gradle中的依赖配置;

    • 同一项目团队内部达成一致的时候;

    • 非代码类的资源文件(比如图片、布局、数据、签名文件等);

    优点:

    • 降低耦合,配置集中,容易扩展,比如Android应用多语言支持;

    • 对象之间的关系一目了然,比如strings.xml;

    • xml配置文件比注解功能齐全,支持的类型更多,比如drawable、style等;

    缺点:

    • 繁琐;

    • 类型不安全,比如R.java中的都是资源ID,用TextView的setText方法时传入int值时无法检测出该值是否为资源ID,但@StringRes可以;

    不过,首先我们还是先看一下一个基本的注解的定义的规范。下面我们自定义了一个名为 UseCase 的注解,可以看出我们用到了上面提及的几种元注解:

    4.2、注解:

    使用场合:

    • 动态配置信息;

    • 代为实现程序逻辑(比如xUtils中的@ViewInject代为实现findViewById);

    • 代码格式检查,比如Override、Deprecated、NonNull、StringRes等,便于IDE能够检查出代码错误;

    优点:

    • 在class文件中,提高程序的内聚性;

    • 减少重复工作,提高开发效率,比如findViewById。

    缺点:

    • 如果对annotation进行修改,需要重新编译整个工程;

    • 业务类之间的关系不如XML配置那样一目了然;

    • 程序中过多的annotation,对于代码的简洁度有一定影响;

    • 扩展性较差;

    @Documented

    style="font-size: 16px;">@ style="font-size: 16px;">Retention style="font-size: 16px;">( style="font-size: 16px;">RetentionPolicy style="font-size: 16px;">. style="font-size: 16px;">RUNTIME style="font-size: 16px;">)

    style="font-size: 16px;">@ style="font-size: 16px;">Target style="font-size: 16px;">( style="font-size: 16px;">value style="font-size: 16px;">= style="font-size: 16px;">{ style="font-size: 16px;">METHOD style="font-size: 16px;">, style="font-size: 16px;"> style="font-size: 16px;">FIELD style="font-size: 16px;">})

    public @ style="font-size: 16px;">interface style="font-size: 16px;"> style="font-size: 16px;">UseCase style="font-size: 16px;"> style="font-size: 16px;">{

    style="font-size: 16px;"> style="font-size: 16px;">public style="font-size: 16px;"> style="font-size: 16px;">int style="font-size: 16px;"> style="font-size: 16px;">id style="font-size: 16px;">();

    style="font-size: 16px;"> style="font-size: 16px;">public style="font-size: 16px;"> style="font-size: 16px;">String style="font-size: 16px;"> style="font-size: 16px;">deion style="font-size: 16px;">() style="font-size: 16px;"> style="font-size: 16px;">default style="font-size: 16px;"> style="font-size: 16px;">"default value" style="font-size: 16px;">;

    }

    五、常用注解库

    • ButterKnife
    • Dagger2
    • Retrofit
    • EventBus
    • Afinal

      开源的Android的orm和ioc应用开发框架,其特点是小巧灵活,代码入侵量少。在android应用开发中,通过Afinal的ioc框架,诸如ui绑定,事件绑定,通过注解可以自动绑定。通过Afinal的orm框架,无需任何配置信息,一行代码就可以对android的sqlite数据库进行增删改查操作。同时,Afinal内嵌了finalHttp等简单易用的工具,可以轻松的对http就行求情的操作。

    这是一个普通的注解的定义。从上面我们也可以总结出,在定义注解的时候,有以下几个地方需要注意:

    六、Annotation 分类

    1. 使用 @interface 声明并且指定注解的名称;
    2. 注解的定义类似于接口中的方法的定义,但要注意两者之间本质上是不同的;
    3. 可以通过 default 为指定的元素指定一个默认值,如果用户没有为其指定值,就使用默认值。

    6.1、 标准 Annotation

    包括 Override, Deprecated, SuppressWarnings,标准 Annotation 是指 Java 自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning

    6.2、元 Annotation

    @Retention, @Target, @Inherited, @Documented,元 Annotation 是指用来定义 Annotation 的 Annotation

    2. 元注解

    6.2.1、@Retention(英文:保留)

    用于指定被修饰的Annotation可以保留多长时间,只能修饰Annotation定义。

    @Retention包含一个RetentionPolicy类型的value成员变量,使用@Retention必须为该value成员变量指定值。value成员变量的值有3个选择:

    • RetentionPolicy.CLASS: 编译器将把Annotation记录在class文件中。当运行java程序时,JVM不可获取Annotation信息。(默认值)
    • RetentionPolicy.RUNTIME: 编译器将把Annotation记录在class文件中。当运行java程序时,JVM也可获取Annotation信息,程序可以通过反射获取该Annotation信息
    • RetentionPolicy.SOURCE: Annotation只保留在源代码中(.java文件中),编译器直接丢弃这种Annotation。

    比如:

    //定义下面的MyAnnotaion保留到运行时,也可以使用value=RetentionPolicy.RUNTIME
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotaion{}
    

    好的,看完了一个基本的注解的定义,我们来看一下上面用到的 Java 元注解的含义。

    6.2.2、@Target (目标)

    用于指定被修饰的Annotation能用于修饰哪些程序单元,只能修饰Annotation定义。它包含一个名为value的成员变量,取值如下:

    • @Target(ElementType.ANNOTATION_TYPE): 指定该该策略的Annotation只能修饰Annotation.
    • @Target(ElementType.TYPE) : 接口、类、枚举、注解
    • @Target(ElementType.FIELD) : 成员变量(字段、枚举的常量)
    • @Target(ElementType.METHOD) : 方法
    • @Target(ElementType.PARAMETER): 方法参数
    • @Target(ElementType.CONSTRUCTOR): 构造函数
    • @Target(ElementType.LOCAL_VARIABLE): 局部变量
    • @Target(ElementType.PACKAGE): 修饰包定义
    • @Target(ElementType.TYPE_PARAMETER): java8新增,可以使用在方法参数上
    • @Target(ElementType.TYPE_USE): java8新增,修饰的注解称为Type Annotation(类型注解),Type Annotation可用在任何用到类型的地方。

    比如:

    @Target(ElementType.FIELD)
    public @interface MyActionListener{}
    

    @Target

    6.2.3、@Documented

    用于指定被修饰的Annotation将被javadoc工具提取成文档。即说明该注解将被包含在javadoc中。

    @Target 用来指定注解能够修饰的对象的类型。因为 @Target 本身也是一个注解,所以你可以在源码中查看它的定义。该注解接收的参数是一个 ElementType 类型的数组,所以,就是说我们自定义的注解可以应用到多种类型的对象,而对象的类型由 ElementType 定义。 ElementType 是一个枚举,它的枚举值如下:

    6.2.4、@Inherited

    用于指定被修饰的Annotation具有继承性。即子类可以继承父类中的该注解。比如:注解@TestAnnotation被元注解@Inherited修饰,把@TestAnnotation添加在类Base上,则Base的所有子类也将默认使用@TestAnnotation注解。

    • TYPE:类、接口或者enum声明
    • FIELD:域声明,包括enum实例
    • METHOD:方法声明
    • PARAMETER:参数声明
    • CONSTRUCTOR:构造器声明
    • LOCAL_VARIABLE:局部变量声明
    • ANNOTATION_TYPE:注解声明
    • PACKAGE:包声明
    • TYPE_PARAMETER:类型参数声明
    • TYPE_USE:使用类型

    6.2.5、Repeatable(可重复)

    Java SE8引入的注解,表示这个注解可以在同一处多次声明

    6.3、 自定义 Annotation

    自定义 Annotation 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation
    这里只是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation

    所以,比如根据上面的内容,我们可以直到我们的自定义注解 @UseCase 只能应用于方法和字段。

    七、如何自定义注解

    首先,我们需要先了解注解处理器Processor,注解处理器有什么作用呢?首先它会在编译期被调用,可以扫描特定注解的信息,你可以为你自己的的注解注册处理器,一个特定的注解处理器以java源码作为输入,然后生成一些文件作(通常为java)为输出,这些java文件同样会被编译。这意味着,你可以根据注解的信息和被注解类的信息生成你想生成的代码!

    需求:

    定义一个注解MyAnnotation,去注解MainActivity,然后处理器扫描生成一个java文件,这个java文件有个输出Hello MyAnnotation的方法,运行的我们的MainAcitivity,然后调用这个java文件的方法。

    @Retention

    7.1、创建注解工程

    同样我们先创建一个Java工程,编写一个注解类MyAnnotation

    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.TYPE)
    public @interface MyAnnotation {
        String value() default "MyAnnotation";
    }
    

    用来指定注解的保留策略,比如有一些注解,当你在自己的代码中使用它们的时候你会把它写在方法上面,但是当你反编译之后却发现这些注解不在了;而有些注解反编译之后依然存在,发生这种情况的原因就是在使用该注解的时候指定了不同的参数。

    7.2、创建Android工程

    定义好我们注解的MyAnnotation,接下来,我们要用这个去注解MainActivity,现在我们是在Java工程,那么我们新创建一个Android工程,里面有个MainActivity,这个工程依赖我们MyAnnotation所在的工程。

    @MyAnnotation
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

    接下来我们就要通过自己定义的注解处理器去扫描这个注解进而生成java文件,但是在此之前,我们需要先了解注解处理的工作流程和相关API。

    与 @Target 相同的是这个注解也使用枚举来指定值的类型,不同的是它只能指定一个值,具体可以看源码。这里它使用的是 RetentionPolicy 枚举,它的几个值的含义如下:

    7.3、创建Compiler工程

    AbstractProcessor

    AbstractProcessor就是系统抽象出来的处理器类,如果我们要处理自己定义的注解,就必须借助于它。
    例如:

    public class MyProcessor extends AbstractProcessor{
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            return super.getSupportedAnnotationTypes();
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return super.getSupportedSourceVersion();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            return false;
        }
    
    }
    

    我们需要重写的方法有:

    • init(ProcessingEnvironment processingEnv) : 所有的注解处理器类都必须有一个无参构造函数。然而,有一个特殊的方法init(),它会被注解处理工具调用,以ProcessingEnvironment作为参数。ProcessingEnvironment 提供了一些实用的工具类Elements, Types和Filer。我们在后面将会使用到它们。

    • process(Set<? extends TypeElement> annoations, RoundEnvironment env) : 这类似于每个处理器的main()方法。你可以在这个方法里面编码实现扫描,处理注解,生成 java 文件。使用RoundEnvironment 参数,你可以查询被特定注解标注的元素。

    • getSupportedAnnotationTypes(): 在这个方法里面你必须指定哪些注解应该被注解处理器注册。注意,它的返回值是一个String集合,包含了你的注解处理器想要处理的注解类型的全称。换句话说,你在这里定义你的注解处理器要处理哪些注解。

    • getSupportedSourceVersion() : 用来指定你使用的 java 版本,建议使用SourceVersion.latestSupported()。

    • SOURCE:注解将被编译器丢弃
    • CLASS:注解在class文件中使用,但会被JVM丢弃
    • RUNTIME:VM将在运行期保留注解,故可以通过反射读取注解的信息

    7.4、注册处理器

    我们在编译好的META-INF/services添加我们的处理器路径,谷歌已经提供一个很方便的库,帮助我们做这些东西,我们只需要在处理器工程添加依赖

    compile 'com.google.auto.service:auto-service:1.0-rc2'
    

    然后在Myprocessor中添加@AutoService(Processor.class)的注解,这样就完成了我们处理器的注册。

    图片 1

    image

    编译成生成的META-INF/services中就注册了我们的MyProcessor

    图片 2

    image

    接下来,我们编写一个我们自己的处理器,生成java文件,来讲解一下相关API,以及要注意的事项。

    /**
     * 每一个注解处理器类都必须有一个空的构造函数,默认不写就行;
     */
    @AutoService(Processor.class)
    public class MyProcessor extends AbstractProcessor {
    
        //处理Element的工具类
        private Elements mElementUtils;
        //生成文件的工具
        private Filer mFiler;
        //日志信息的输出
        private Messager mMessager;
    
    
        /**
         * 这相当于每个处理器的主函数main(),你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。
         * 输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素
         * @param annotations   请求处理的注解类型
         * @param roundEnvironment  有关当前和以前的信息环境
         * @return  如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们;
         *          如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们
         */
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
            Set<? extends Element> set = roundEnvironment.getElementsAnnotatedWith(MyAnnotation.class);
            for (Element element : set){
                if(element.getKind() == ElementKind.CLASS){
                    TypeElement typeElement = (TypeElement) element;
                    brewJavaFile(typeElement);
                }
            }
            return true;
        }
    
        /**
         * init()方法会被注解处理工具调用,并输入ProcessingEnviroment参数。
         * ProcessingEnviroment提供很多有用的工具类Elements, Types 和 Filer
         * @param processingEnvironment 提供给 processor 用来访问工具框架的环境
         */
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            mElementUtils = processingEnvironment.getElementUtils();
            mFiler = processingEnvironment.getFiler();
            mMessager = processingEnvironment.getMessager();
        }
    
        /**
         * 这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称
         * @return  注解器所支持的注解类型集合,如果没有这样的类型,则返回一个空集合
         */
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> set = new LinkedHashSet<>();
            set.add(MyAnnotation.class.getCanonicalName());
            return set;
        }
    
        /**
         * 指定使用的Java版本,通常这里返回SourceVersion.latestSupported(),默认返回SourceVersion.RELEASE_6
         * @return  使用的Java版本
         */
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        private void brewJavaFile(TypeElement pElement){
            //sayHello 方法
            MyAnnotation myAnnotation = pElement.getAnnotation(MyAnnotation.class);
            MethodSpec methodSpec = MethodSpec.methodBuilder("sayHello")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC).returns(void.class)
                    .addStatement("$T.out.println($S)",System.class,"Hello"+myAnnotation.value()).build();
    
            // class
            TypeSpec typeSpec = TypeSpec.classBuilder(pElement.getSimpleName().toString()+"$$HelloWorld").addModifiers(Modifier.PUBLIC,Modifier.FINAL).addMethod(methodSpec).build();
            // 获取包路径,把我们的生成的源码放置在与被注解类中同一个包路径中
            JavaFile javaFile = JavaFile.builder(mElementUtils.getPackageOf(pElement).getQualifiedName().toString(),typeSpec).build();
            try {
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    7.5、测试

    新建一个Android工程,该工程依赖注解工程,至于compiler处理器工程,我们要使用apt的方式依赖。
    这里有人要问了,apt是什么?

    它主要有两个作用:

    • 能在编译时期去依赖注解处理器并进行工作,但在生成 APK 时不会包含任何遗留的东西
    • 能够辅助 Android Studio 在项目的对应目录中存放注解处理器在编译期间生成的文件

    了解完apt,那我们就先在项目目录下的build.gradle中添加
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'这个依赖

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.2.1'
            // apt 
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        }
    }
    

    然后在Android 工程中,添加这个插件依赖

    apply plugin: 'com.android.application'
    apply plugin: 'com.neenbedankt.android-apt'
    

    然后就可以使用apt依赖处理器工程了

    apt project(':xs_compiler')
    

    运行我们的Android工程,查看build生成文件

    图片 3

    Paste_Image.png

    顺利生成我们的文件了,剩下就是怎么去调用这个sayHello的方法,我们的思路是通过反射生成的类,调用该方法。

    在注解工程中,新建AnnotationApi类,编码如下

    public class MyAnnotationApi {
    
        public static void sayHelloAnnotation(Object pTarget){
            String name = pTarget.getClass().getCanonicalName();
            try {
                Class clazz = Class.forName(name+"$$HelloWorld");
                Method method = clazz.getMethod("sayHello");
                method.invoke(null);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    然后在MainActivity中调用sayHelloAnnotation的方法

    @MyAnnotation
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main_layout);
            MyAnnotationApi.sayHelloAnnotation(this);
        }
    
    }
    

    查看输出:

    I/System.out: HelloMyAnnotation
    

    完工!

    当我们在 Android 中使用注解的时候,一种是在运行时使用的,所以我们要用 RUNTIME ;另一种是在编译时使用的,所以我们用 CLASS 。

    @Documented、@Inherited 和 @Repeatable

    这三个元注解的功能比较简单和容易理解,这里我们一起给出即可:

    • @Documented 表示此注解将包含在 javadoc 中;
    • @Inherited 表示允许子类继承父类的注解;
    • @Repeatable 是 Java8 中新增的注解,表示指定的注解可以重复应用到指定的对象上面。

    上文,我们回顾了 Java 中注解相关的知识点,相信你已经对注解的内容有了一些了解,那么我们接下来看一下注解在实际开发中的两种应用方式。

    2、注解的两种使用方式

    在我开始为我的开源项目马克笔记编写数据库的时候,我考虑了使用注解来为数据库对象指定字段的信息,并根据这心信息来拼接出创建数据库表的 SQL 语句。当时也想用反射来动态为每个字段赋值的,但是考虑到反射的性能比较差,最终放弃了这个方案。但是,使用注解处理的方式可以完美的解决我们的问题,即在编译的时候动态生成一堆代码,实际赋值的时候调用这些方法来完成。这前后两种方案就是我们今天要讲的注解的两种使用方式。

    2.1 基于反射使用注解

    这里为了演示基于反射的注解的使用方式,我们写一个小的 Java 程序,要实现的目的是:定义两个个注解,一个应用于方法,一个应用于字段,然后我们使用这两个注解来定义一个类。我们想要在代码中动态地打印出使用了注解的方法和字段的信息和注解信息。

    这里我们先定义两个注解,应用于字段的 @Column 注解和应用于方法 @Important 注解:

    @Target(value= {ElementType.FIELD})

    style="font-size: 16px;">@ style="font-size: 16px;">Retention style="font-size: 16px;">( style="font-size: 16px;">RetentionPolicy style="font-size: 16px;">. style="font-size: 16px;">RUNTIME style="font-size: 16px;">)

    style="font-size: 16px;">public style="font-size: 16px;"> style="font-size: 16px;">@ style="font-size: 16px;">interface style="font-size: 16px;">Column style="font-size: 16px;"> style="font-size: 16px;">{

    style="font-size: 16px;"> style="font-size: 16px;">String style="font-size: 16px;"> style="font-size: 16px;">name style="font-size: 16px;">();

    }

    style="font-size: 16px;">@ style="font-size: 16px;">Target style="font-size: 16px;">( style="font-size: 16px;">value= { style="font-size: 16px;">ElementType style="font-size: 16px;">. style="font-size: 16px;">METHOD style="font-size: 16px;">})

    style="font-size: 16px;">@ style="font-size: 16px;">Retention style="font-size: 16px;">( style="font-size: 16px;">RetentionPolicy style="font-size: 16px;">. style="font-size: 16px;">RUNTIME style="font-size: 16px;">)

    style="font-size: 16px;">public style="font-size: 16px;">< style="font-size: 16px;">a style="font-size: 16px;"> style="font-size: 16px;">href style="font-size: 16px;">= style="font-size: 16px;">"http://www.jobbole.com/members/强哥哥" style="font-size: 16px;">> style="font-size: 16px;">@ style="font-size: 16px;">interface style="font-size: 16px;"></ style="font-size: 16px;">a> WrappedMethod style="font-size: 16px;"> style="font-size: 16px;">{

    }

    然后我们定义了一个Person类,并使用注解为其中的部分方法和字段添加注解:

    privatestaticclassPerson{

    style="font-size: 16px;"> style="font-size: 16px;">@ style="font-size: 16px;">Column style="font-size: 16px;">( style="font-size: 16px;">name= "id" style="font-size: 16px;">)

    style="font-size: 16px;"> style="font-size: 16px;">private style="font-size: 16px;"> style="font-size: 16px;">int style="font-size: 16px;"> style="font-size: 16px;">id style="font-size: 16px;">;

    style="font-size: 16px;"> style="font-size: 16px;">@ style="font-size: 16px;">Column style="font-size: 16px;">( style="font-size: 16px;">name= "first_name" style="font-size: 16px;">)

    style="font-size: 16px;"> style="font-size: 16px;">private style="font-size: 16px;"> style="font-size: 16px;">String style="font-size: 16px;"> style="font-size: 16px;">firstName style="font-size: 16px;">;

    style="font-size: 16px;"> style="font-size: 16px;">@ style="font-size: 16px;">Column style="font-size: 16px;">( style="font-size: 16px;">name= "last_name" style="font-size: 16px;">)

    style="font-size: 16px;"> style="font-size: 16px;">private style="font-size: 16px;"> style="font-size: 16px;">String style="font-size: 16px;"> style="font-size: 16px;">lastName style="font-size: 16px;">;

    style="font-size: 16px;"> style="font-size: 16px;">private style="font-size: 16px;"> style="font-size: 16px;">int style="font-size: 16px;"> style="font-size: 16px;">temp style="font-size: 16px;">;

    style="font-size: 16px;"> style="font-size: 16px;">@ style="font-size: 16px;">WrappedMethod style="font-size: 16px;">()

    style="font-size: 16px;"> style="font-size: 16px;">public style="font-size: 16px;"> style="font-size: 16px;">String style="font-size: 16px;"> style="font-size: 16px;">getInfo style="font-size: 16px;">() style="font-size: 16px;"> style="font-size: 16px;">{

    style="font-size: 16px;"> style="font-size: 16px;">return style="font-size: 16px;"> style="font-size: 16px;">id+ " :" style="font-size: 16px;">+ style="font-size: 16px;">firstName style="font-size: 16px;">+ " "+ style="font-size: 16px;">lastName style="font-size: 16px;">;

    style="font-size: 16px;"> style="font-size: 16px;">}

    style="font-size: 16px;"> style="font-size: 16px;">public style="font-size: 16px;"> style="font-size: 16px;">String style="font-size: 16px;"> style="font-size: 16px;">method style="font-size: 16px;">() style="font-size: 16px;"> style="font-size: 16px;">{

    style="font-size: 16px;"> style="font-size: 16px;">return style="font-size: 16px;"> style="font-size: 16px;">"Nothing" style="font-size: 16px;">;

    style="font-size: 16px;"> style="font-size: 16px;">}

    }

    然后,我们使用Person类来获取该类的字段和方法的信息,并输出具有注解的部分:

    style="font-size: 16px;">publicstaticvoidmain(String...args){

    style="font-size: 16px;"> style="font-size: 16px;">Class style="font-size: 16px;">< style="font-size: 16px;">?> c style="font-size: 16px;">= style="font-size: 16px;">Person style="font-size: 16px;">. style="font-size: 16px;">class style="font-size: 16px;">;

    style="font-size: 16px;"> style="font-size: 16px;">Method style="font-size: 16px;">[] style="font-size: 16px;"> style="font-size: 16px;">methods style="font-size: 16px;">= style="font-size: 16px;">c style="font-size: 16px;">. style="font-size: 16px;">getDeclaredMethods style="font-size: 16px;">();

    style="font-size: 16px;"> style="font-size: 16px;">for style="font-size: 16px;"> style="font-size: 16px;">(Method method style="font-size: 16px;">: style="font-size: 16px;">methods style="font-size: 16px;">) style="font-size: 16px;"> style="font-size: 16px;">{

    style="font-size: 16px;"> style="font-size: 16px;">if style="font-size: 16px;"> style="font-size: 16px;">( style="font-size: 16px;">method style="font-size: 16px;">. style="font-size: 16px;">getAnnotation style="font-size: 16px;">( style="font-size: 16px;">WrappedMethod style="font-size: 16px;">. style="font-size: 16px;">class style="font-size: 16px;">)!= null style="font-size: 16px;">) style="font-size: 16px;"> style="font-size: 16px;">{

    style="font-size: 16px;"> style="font-size: 16px;">System style="font-size: 16px;">. style="font-size: 16px;">out style="font-size: 16px;">. style="font-size: 16px;">print style="font-size: 16px;">( style="font-size: 16px;">method style="font-size: 16px;">. style="font-size: 16px;">getName style="font-size: 16px;">()+ " " style="font-size: 16px;">);

    style="font-size: 16px;"> style="font-size: 16px;">}

    style="font-size: 16px;"> style="font-size: 16px;">}

    style="font-size: 16px;"> style="font-size: 16px;">System style="font-size: 16px;">. style="font-size: 16px;">out style="font-size: 16px;">. style="font-size: 16px;">println style="font-size: 16px;">();

    style="font-size: 16px;"> style="font-size: 16px;">Field style="font-size: 16px;">[] style="font-size: 16px;"> style="font-size: 16px;">fields= c style="font-size: 16px;">. style="font-size: 16px;">getDeclaredFields style="font-size: 16px;">();

    style="font-size: 16px;"> style="font-size: 16px;">for style="font-size: 16px;"> style="font-size: 16px;">(Field field style="font-size: 16px;">: style="font-size: 16px;">fields style="font-size: 16px;">) style="font-size: 16px;"> style="font-size: 16px;">{

    style="font-size: 16px;">Column column style="font-size: 16px;">= style="font-size: 16px;">field style="font-size: 16px;">. style="font-size: 16px;">getAnnotation style="font-size: 16px;">( style="font-size: 16px;">Column style="font-size: 16px;">. style="font-size: 16px;">class style="font-size: 16px;">);

    style="font-size: 16px;"> style="font-size: 16px;">if style="font-size: 16px;"> style="font-size: 16px;">( style="font-size: 16px;">column style="font-size: 16px;">!= style="font-size: 16px;">null style="font-size: 16px;">) style="font-size: 16px;"> style="font-size: 16px;">{

    style="font-size: 16px;"> style="font-size: 16px;">System style="font-size: 16px;">. style="font-size: 16px;">out style="font-size: 16px;">. style="font-size: 16px;">print style="font-size: 16px;">( style="font-size: 16px;">column style="font-size: 16px;">. style="font-size: 16px;">name style="font-size: 16px;">()+ "-" style="font-size: 16px;">+ style="font-size: 16px;">field style="font-size: 16px;">. style="font-size: 16px;">getName style="font-size: 16px;">()+ ", " style="font-size: 16px;">);

    style="font-size: 16px;"> style="font-size: 16px;">}

    style="font-size: 16px;"> style="font-size: 16px;">}

    }

    输出结果:

    getInfo

    style="font-size: 16px;">id style="font-size: 16px;">- style="font-size: 16px;">id style="font-size: 16px;">, style="font-size: 16px;"> style="font-size: 16px;">first_name style="font-size: 16px;">- style="font-size: 16px;">firstName style="font-size: 16px;">, style="font-size: 16px;"> style="font-size: 16px;">last_name style="font-size: 16px;">- style="font-size: 16px;">lastName style="font-size: 16px;">,

    在上面的代码的执行结果,我们可以看出:使用了注解和反射之后,我们成功的打印出了使用了注解的字段。这里我们需要先获取指定的类的 Class 类型,然后用反射获取它的所有方法和字段信息并进行遍历,通过判断它们的 getAnnotation() 方法的结果来确定这个方法和字段是否使用了指定类型的注解。

    上面的代码可以解决一些问题,但同时,我们还有一些地方需要注意:

    1. 如果指定的方法或者字段名被混淆了怎么办? 对于一些可以自定义名称的情景,我们可以在注解中加入参数为该字段或者方法指定一个名称;
    2. 上面使用了很多的反射,这会影响程序的性能吗? 使用注解的方式肯定性能不会高,但是如果注解的使用没有那么频繁,上面方法不会有特别大的性能损耗,比如拼接 SQL 这样的操作,可能只需要执行一次。不过,根本的解决办法是使用注解的第二种使用方式!

    2.2 基于 annotationProcessor 使用注解

    也许你之前已经使用过 ButterKnife 这样的注入框架,不知道你是否记得在 Gradle 中引用它的时候加入了下面这行依赖:

    style="font-size: 16px;">annotationProcessor'com.jakewharton:butterknife-compiler:8.8.1'

    这里的 annotationProcessor 就是我们这里要讲的注解处理。本质上它会在编译的时候,在你调用 ButterKnife.bind(this); 方法的那个类所在的包下面生成一些类,当调用 ButterKnife.bind(this); 的时候实际上就完成了为使用注解的方法和控件绑定的过程。也就是,本质上还是调用了 findViewById() ,只是这个过程被隐藏了,不用你来完成了,仅此而已。

    下面,我们就使用注解处理的功能来制作一个类似于 ButterKnife 的简单库。不过,在那之前我们还需要做一些准备——一些知识点需要进行说明。即Javapoet和 AbstractProcessor 。

    Javapoet & AbstractProcessor

    Javapoet 是一个用来生成 .java 文件的 Java API,由 Square 开发,你可以在它的 Github 主页中了解它的基本使用方法。它的好处就是对方法、类文件和代码等的拼接进行了封装,有了它,我们就不用再按照字符串的方式去拼接出一段代码了。相比于直接使用字符串的方式,它还可以生成代码的同时直接 import 对应的引用,可以说是非常方便、快捷的一个库了。

    这里的 AbstractProcessor 是用来生成类文件的核心类,它是一个抽象类,一般使用的时候我们只要覆写它的方法中的4个就可以了。下面是这些方法及其定义:

    本文由www.85058.com发布于技术支持,转载请注明出处:Java 注解及其在 Android 中的应用

    关键词: