深入java理解之注解

转载请注明本文出自远古大钟的博客(http://blog.csdn.net/duo2005duo),谢谢支持!

文章一:

简介

注解(annotation)是JDK5之后引进的新特性,是一种特殊的注释,之所以说它特殊是因为不同于普通注释(comment)能存在于源码,而且还能存在编译期跟运行期,会最终编译成一个.class文件,所以注解能有比普通注释更多的功能

使用Java自带的注解

这里只讲Java5 自带的3种注解

  • @Deprecated 表明当前的元素已经不推荐使用
/**
* 一般来说使用@Deprecated注解的方法要加上一下的javadoc文档
* @deprecated 这里要写明因为什么原因抛弃了使用该方法,推荐使用什么方法代替
*/
@Deprecated
public void badMethod(){ //已经不推荐使用的方法
...
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • @Override 表明当前方法是覆盖了父类方法,添加@Overrride是一种必须的编程习惯,因为帮助你找到代码中的错误,考虑以下情况
abstract class Fruit{
    protected void showOnTheDesk(){
        ...
    }

}
class Apple implements Fruit{
    @Override
    public void showInTheDesk(){ //编译报错,看出来没有?父类是On不是In!
        ...
    }  
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

以上代码在编译时报错the showInTheDesk() must oerride or implement a supertype method,所以@Override帮助你避免写错方法名

  • @SuppressWarnings 关闭不当的编译器警告信息,如下
class A{
    final void doSth(){
        //添加说明抑制警告的原因:A的getClass() 必然是Class<A>
        @SuppressWarnings(unchecked)
        Class<A> a=(Class<A>)getClass()  //有unchecked警告
        ...
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

自定义注解

简单声明

注解的声明相对简单,有点类似接口的写法

@interface Empty{
    String field1() default "it's empty"; //default 设置默认值
}
@Empty   //使用 default默认值"it's empty"
class C{
}

//如果没有使用default设置默认值的话,field必须显式传入参数,如下
@Empty(field1="empty A")
class A{
}

@interface Value{
    String value();
    String other() default "it's great";
}
@Value("empty B") //当注解有一个方法value时,没有其他方法或者其他方法有默认值,不需要指明具体方法
class B{
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

以上是Java自带的一些注解,Java同时提供了另外一类注解,叫元注解。元注解可以用来设置注解,自定义注解一般都会使用到元注解

元注解

‘普通注解’只能用来注解’代码’,而’元注解’只能用来注解 ‘普通注解’。 
JDK5时支持的元注解有@Documented @Retention @Target @Inherited,接下来分别介绍它们修饰注解的效果

让注解自动加入Javadoc

@Documented 被修饰的注解会生成到javadoc中

@Documented
@interface DocumentedAnnotation{ 
}
@interface UnDocumentedAnnotation{
}
@DocumentedAnnotation
@UnDocumentedAnnotation
class A{}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
代码中注解DocumentedAnnotation添加了元注解@Documented,而UnDocumentedAnnotation则没有添加,通过'javadoc A.java'命令生成Javadoc如下图。可以看到,在A类的Javadoc中只能看到@DocumentedAnnotation注解,但是无法看到@UnDocumentedAnnotation注解

这里写图片描述

设置注解的级别

使用@Retention可以设置注解的级别

//RetentionPolicy是一个枚举类,默认是CLASS,此外还可选择SOURCE,RUNTIME
@Retention(RetentionPolicy.CLASS) 
@interface ClassRetentionAnnotation{}
 
 
  • 1
  • 2
  • 3

按照Retention的三种枚举类型,注解分为三种级别,每个级别有其特定的功能

  • SOURCE代表源码级别,注解只存在源码中,其功能是与编译器交互,用于代码检测,如@Override,@SuppressWarings,许多框架如Dragger就是使用这个级别的注解,这个级别的框架额外效率损耗发生在编译时
  • CLASS代表字节码级别,注解存在源码与字节码文件中,主要用于编译时生成而外的文件,如XML,Java文件等,这个级别需要添加JVM加载时候的代理(javaagent ),使用代理来动态修改字节码文件(由于Android虚拟机并不支持所以本专题不会再做介绍,在Android中可以使用aspectJ来实现类似这个级别的功能)
  • RUNTIME代表运行时级别,注解存在源码,字节码与Java虚拟机中,主要用于运行时反射获取相关信息,许多框架如OrmLite就是使用这个级别的注解,这个级别的框架额外的效率损耗发生在程序运行时

限制注解的元素种类

不加元注解@Target的情况下,注解可以修饰各种元素,比如可以修饰类,可以修饰变量,可以修饰方法等,但是如果要限制注解的元素种类,比如限制只能修饰方法,则需要加入@Target,一下是@Override的源码

@Target(ElementType.METHOD) //限制了注解只能修饰方法,ElementType是一个枚举类
@Retention(RetentionPolicy.SOURCE)
public @interface Override{
}
 
 
  • 1
  • 2
  • 3
  • 4

可以看到@Override源码中已经限定了只能修饰方法。

让注解可以被继承

@Inherited可以让注解类似被继承一样,但是这并不是真的继承。通过使用@Inherited,只可以让子类类对象使用getAnnotations()反射获取父类被@Inherited修饰的注解。

@Inherited
@Retention(RetentionPolicy.Runtime)
@interface Inheritable{
}
@interface UnInheritable{
}
public class Test{
    @UnInheritable
    @Inheritable
    public static class Super{
    }
    public static class Sub extends  Super {
    }
    public static void main(String... args){
        Super instance=new Sub();
        System.out.println(Arrays.toString(instance.getClass().getAnnotations()));
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
输出:[Inheritable]

因为这种伪继承机制,被@Documented与@Inherited同时修饰的注解来注解某个类,其子类Javadoc并不会出现这个注解的提示,可以使用Javadoc命令执行以上代码,结果如下: 
这里写图片描述这里写图片描述

后续

自定义注解可以搭建框架,如何使用自定义注解搭建框架,后续将分Runtime级别跟Source级别介绍 
Java注解(2)-运行时框架 
Java注解(3)-源码级框架

附录

IDE会有提示,所以作为附录

SuppressWarning参数: 
- all to suppress all warnings 
- boxing to suppress warnings relative to boxing/unboxing operations 
- cast to suppress warnings relative to cast operations 
- dep-ann to suppress warnings relative to deprecated annotation 
- deprecation to suppress warnings relative to deprecation 
- fallthrough to suppress warnings relative to missing breaks in switch statements 
- finally to suppress warnings relative to finally block that don’t return 
- hiding to suppress warnings relative to locals that hide variable 
- incomplete-switch to suppress warnings relative to missing entries in a switch statement (enum case) 
- nls to suppress warnings relative to non-nls string literals 
- null to suppress warnings relative to null analysis 
- rawtypes to suppress warnings relative to un-specific types when using generics on class params 
- restriction to suppress warnings relative to usage of discouraged or forbidden references 
- serial to suppress warnings relative to missing serialVersionUID field for a serializable class 
- static-access to suppress warnings relative to incorrect static access 
- synthetic-access to suppress warnings relative to unoptimized access from inner classes 
- unchecked to suppress warnings relative to unchecked operations 
- unqualified-field-access to suppress warnings relative to field access unqualified 

- unused to suppress warnings relative to unused code


文章二:


本文主要介绍如何使用Java运行时级别的注解配合反射来搭建框架,以下是构建sql表的简单例子虽然不能运用到实际中,但是阐明了搭建运行时框架的方法。源码已经上传至github,链接 
如果你对注解的相关基础不了解,可以先阅读 Java注解(1)-基础


实现功能

先看一下要实现的效果,通过给数据类Bean添加相应的注解@Table ,@Column,就可以获取到创建表的sql语句

@Table(name="BeanTable") //注解@Table 具体实现接下来会讲
class Bean{
    @Column(name="field") //注解@Colomn 具体实现接下来会讲
    int field;
    @Column(name="description")
    String description;
}

public class Test{
    public static void main(String... args){
        //Utils类是我们需要定义的框架工具,接下来会讲
        System.out.println(Utils.createTable(Bean.class));
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

执行输出

create table BeanTable(field interger,description text)
 
 
  • 1

自定义注解

由于我们搭建的是运行时框架,需要再运行时通过反射来进行,所以注解的级别必须设置成Runtime级别,这样运行时才能反射到相应的注解 
通过下面的定义,我们就能在运行时动态获取@Table与@Retention两个注解与其对应的名称

  • 定义对应表的注解
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
    String name(); //name用来设置表名
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 定义对应字段的注解
@Retention(RetentionPolicy.RUNTIME)
@interface Column{
    String name(); //name用来设置字段名
}
 
 
  • 1
  • 2
  • 3
  • 4

运行时获取注解并转化

基础API

AnnotatedElement代表能够被注解的元素,如方法,成员变量,方法参数,包,类都是这个接口的实现,AnnotatedElement有方法如下表:

方法名 用法
Annotation getAnnotation(Class annotationType) 获取注解在其上的annotationType
Annotation[] getAnnotations() 获取所有注解
isAnnotationPresent(Class annotationType) 判断当前元素是否被annotationType注解
Annotation[] getDeclareAnnotations() 与getAnnotations() 类似,但是不包括父类中被Inherited修饰的注解

返回的Annotation是注解的实例,可以反射Method获取需要的值,即是获取@Column或者@Table中name的值。

框架工具的实现

我们通过反射技术,运行时获取注解,从而得到Bean类对应的数据库的表的建表sql语句

获取表名

如下代码,先判断Bean类是否有注解@Table,如果有则获取@Table对象并得到name方法的值

    private static String getTableName(Class<?> bean) {
        String name = null;
        //判断是否有Table注解
        if (bean.isAnnotationPresent(Table.class)) {
            //获取注解对象
            Table table = bean.getAnnotation(Table.class);
            name =table.name();
        }
        return name;
    }

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

获取字段名与类型

逐个分析Bean的成员变量是否有被@Column注解,有则获取其对应的字段名与类型

private static List<NameAndType> getColumns(Class<?> bean) {
        List<NameAndType> columns = new ArrayList<NameAndType>();
        Field[] fields = bean.getDeclaredFields();
        if (fields != null) {
            //分析Bean中的变量是否需要生成sql字段
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                if (field.isAnnotationPresent(Column.class)) {
                    //生成sql字段的名
                    Column column = field.getAnnotation(Column.class);
                    String name=column.name();
                    //生成sql字段的类型
                    String type = null;
                    if (int.class.isAssignableFrom(field.getType())) {
                        type = "integer";
                    } else if (String.class.isAssignableFrom(field.getType())) {
                        type = "text";
                    } else {
                        throw new RuntimeException("unspported type=" + field.getType().getSimpleName());
                    }
                    columns.add(new NameAndType(type, name));

                }

            }
        }
        return columns;
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

生成建表sql语句

生成sql表语句比较简单,主要是把前两部份获取的表名与字段结合起来组合成sql语句

public static String createTable(Class<?> bean) {
    String tableName = getTableName(bean);
    List<NameAndType> columns = getColumns(bean);
    if (tableName != null && !tableName.equals("") && !columns.isEmpty()) {
        StringBuilder createTableSql = new StringBuilder("create table ");
        //加表名
        createTableSql.append(tableName);
        createTableSql.append("(");

        //加表中字段
        for (int i = 0; i < columns.size(); i++) {
            NameAndType column = columns.get(i);
            createTableSql.append(column.name);
            createTableSql.append(" ");
            createTableSql.append(column.type);
            // 追加下一个字段定义前需要添加逗号
            if (i != columns.size() - 1) {
                createTableSql.append(",");
            }
        }
        createTableSql.append(")");
        return createTableSql.toString();
    }

    return null;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

优势与缺点

用运行时注解来搭建框架相对容易而且适用性也比较广,搭建的框架使用起来也比较简单。对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping)框架通常使用运行时注解来搭建,但是在此基础上的框架因为需要用到反射,其效率相对与不高。所以因为效率考虑,许多框架不使用运行时注解来搭建而是用源码级别注解来搭建,当然,使用源码级别框架也是有代价的,由于它只能在预编译期间生成额外代码而无法运行时反射操作,复杂度相对较高而且灵活性也相对较低。如何使用源码级别注解框架来搭建会在下一节Java注解(3)-源码级框架 介绍。

文章三:

介绍

前面文章(Java注解(2)-运行时框架 )介绍的运行时框架是在虚拟机运行程序时使用反射技术搭建的框架;而源码级框架是在javac编译源码时,生成框架代码或文件。因为源码级别框架发生过程是在编译期间,所以并不会过多影响到运行效率。因此,搭建框架时候应该优先考虑使用源码级别框架。

注解处理器

注解处理器能够在编译源码期间扫描java代码中的注解,并且根据相关注解动态生成相关的文件。之后在程序运行时就可以使用这些动态生成的代码。值得注意的是,注解处理器运行在跟最终程序不同的虚拟机,也就是说,编译器为注解处理器开启了另外一台虚拟机来运行注解处理器。

步骤

要为工程添加一个注解处理器,需要以下几步

实现AbstractProcessor

package com.example;
public class ProcessorA extends AbstractProcessor {
    ...
}
 
 
  • 1
  • 2
  • 3
  • 4

生成注解处理器jar


对于eclipse, 
jar的目录结构如下

MyProcessor.jar
    - com
        - example
            - ProcessorA.class

    - META-INF
        - services
            - javax.annotation.processing.Processor
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

META-INF中的文件需要自己手动创建,其中文件javax.annotation.processing.Processor里写明需要运行的处理器的类名,如

com.example.ProcessorA
com.example.ProcessorB
com.example.ProcessorC
com.example.ProcessorAndSoOn
 
 
  • 1
  • 2
  • 3
  • 4

选中需要生成jar的工程,右击–>Export–>JarFile,如下图 
eclipsejar


对于Android studo 
1.注解器模块配置如下

apply plugin: 'java'
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
dependencies {
  compile 'com.google.auto.service:auto-service:1.0-rc2'
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.Android studio会自动生成为javax.annotation.processing.Processor,只需为注解器添加@AutoService注解,这样就可以自动生成META-INF内的文件,如下

@AutoService(Processor.class)
public class ProcessorA extends AbstractProcessor{
    ...
}
 
 
  • 1
  • 2
  • 3
  • 4

指定注解器jar

这步主要是让编译器在编译时知道某些jar包是注解器jar,下面分命令行,eclipse和AndroidStudio三种情况分别介绍


  • 命令行的方式
javac -processorpath 注解处理器.jar  目标程序.java
 
 
  • 1

  • eclipse的配置 
    第一步 
    注解处理器eclipse配置1 
    第二步 
    注解处理器eclipse配置2

  • Android studio配置

工程的build.gradle:

dependencies {
     // 其他classpath
     classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'  //添加apt插件
 }
 
 
  • 1
  • 2
  • 3
  • 4

模块的build.gradle:

applying plugin: 'com.android.application'  
apply plugin: 'com.neenbedankt.android-apt' //使用apt插件
dependencies {
    apt '注解处理器.jar'  //注解处理器
    compile '关于处理器生成文件的调用包.jar'   //API包
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注解处理器接口

注解处理一般均需要重写以下四个方法

public class ProcessorA extends AbstractProcessor {
    //这个方法主要是获取工具类
    public synchronized void init(ProcessingEnvironment env){ }


    //这个方法里面写处理的过程,输入参数annotations是getSupportedAnnotationTypes()的子集,即是工程代码中所有注解集合与getSupportedAnnotationTypes()的交集,RoundEnvironment env代表这一轮扫描后的结果,返回true则表示消费完此次扫描,此轮扫描注解结束
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { }


    //返回需要处理的注解的类的getCanonicalName()集合
    public Set<String> getSupportedAnnotationTypes() { }


    //返回SourceVersion.latestSupported()即可
    public SourceVersion getSupportedSourceVersion() { }

}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

PS:java7之后,getSupported***()方法可以用一下注解代替

@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
   AnnotationA.class.getCanonicalName(),AnnotationB.class.getCanonicalName(),
 })
 public class ProcessorA extends AbstractProcessor {
    ... 
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

打印日志

注解处理器的打印日志全部应该交由Messager处理,并且需要在打印错误时利用异常抛出来终止处理器

Messager messager=processingEnv.getMessager()
//打印非error级别log
messager.printMessage(Kind.NOTE,message);

//打印error时抛出异常来中断注解处理器
messager.printMessage(Kind.ERROR,error);
throw new RuntimeException(error);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

扫描处理流程

注解处理器对工程代码的扫描是多次的,可以注意到AbstractProcessor的process()方法的输入参数有一个是RoundEnvironment,这个代表一次扫描的结果。 
影响注解处理器执行顺序与逻辑的地方有三处 
1.javax.annotation.processing.Processor中的书写顺序决定注册处理器的执行顺序 
假设该文件中定义如下:

package.ProcessorB
package.ProcessorA
 
 
  • 1
  • 2

那么编译器每一轮扫描会先执行处理器ProcessorB,再执行处理器ProcessorA

2.AbstractProcessor中processor()方法的返回值决定是否要终结当前轮的处理 
按照1中注册的顺序,假如ProcessorB中的process()方法返回true,则表示消费完这轮的注解扫描,将不再执行ProcessorA,只有当返回false时,才会接下来执行ProcessorB

3.没有输出文件跟输入文件时扫描结束 
假设按照1中的注册顺序,ProcessorB中的process()方法返回true,并且ProcessorB在第一轮扫描会生成按文件GenerateB.java,则将在第三轮扫描后结束注解处理,过程如下

过程 输入文件 输出文件
第一轮 原工程 GenerateB.java
第二轮 GenerateB.java
第三轮

第三轮时编译器发现没有输出文件也没有输入文件,处理结束 
PS:每个注解处理器在整个过程中都保持同一个实例

分包机制

注解处理器会生成一些代码文件,我们会写一些API调用这些代码。所以,源码级框架除了写注解处理器外,还要写一个API包。另外,由于源码级别的注解并不需要模块划分时候应该将注解处理器跟API分成两个模块来写,有利于缩小编译后体积。(以下简称其所在的包分别是处理器包 跟 API包 )

处理器包使用周期与API包不同:

  • 处理器包:注解处理器只需要在目标工程编译时候执行,运行时不需要
  • API包:API包因为会被目标工程运行时调用,所以在目标工程编译时和运行时都是需要的

由于这些不同,处理器包跟API包在工程中有不同的引用形式,如下

dependencies {
    apt '处理器包'//注解处理器包,只存在编译时
    compile 'API包'   //API包,编译时跟编译后都存在
}
 
 
  • 1
  • 2
  • 3
  • 4

语言模型包的使用

Mirror

注解处理器因为操作的是源码,所以需要用到JAVA语言模型包,javax.lang.model及其子包都是Java的语言模型包。这个包是采用了Mirror设计,Java是一种可以自描述的语言,其反射机制就是一种自描述,传统的反射机制将自描述与其他操作合并在一起,Mirror机制将自描述跟其他操作隔离,自描述部分是Meta level,其他部分是Base level 
这里写图片描述

转化

Element代表语言元素,比如包,类,方法等,但是Element并没有包含自身的信息,自身信息要通过Mirror来获取,每个Element都指向一个TypeMirror,这个TypeMirror里有自身的信息。通过下面获方法取Element中的Mirror

TypeMirror mirror=element.asType() 
 
 
  • 1

TypeMirror类型是DeclaredType或者TypeVariable时候可以转化成Element

Types types=processingEnvironment.getTypeUtils()
Element element=types.asElement(typeMirror)
 
 
  • 1
  • 2

获取类型

获取Element或者TypeMirror的类型都是通过getKind()获取类型,但是返回值虽然不同,但是都是枚举。

ElementKind element.getKind()
TypeKind typeMirror.getKind()
 
 
  • 1
  • 2

针对具体类型,要避免用instanceof,因为即使同一个类getKind()也有不同的结果。比如TypeElemnt的getKind()返回结果可以是枚举,类,注解,接口四种。

源码中移动

比如一个类里面有方法,成员变量等,这个类相对于方法跟成员变量就是外层元素,而成员变量和方法相对于类就是内层元素。 
Element是代表源码的类,源码中移动到其他位置必须是用Element,比如移动到当前元素的外层元素TypeElement

public static TypeElement findEnclosingTypeElement( Element e ){
       while( e != null && !(e instanceof TypeElement) )       {
           e = e.getEnclosingElement();//通过getEnclosingElement()获取外层元素
       }
       return TypeElement.class.cast( e );
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

也可以使用getEnclosedElements()获取当前Element内层的所有元素。

类继承树结构中移动

TypeMirror是用来反应类本身信息的类,在继承树移动必须用到TypeMirror,比如查找某个类的父类

Types typeUtils = processingEnv.getTypeUtils();
while (!typeElement.toString().equals(Object.class.getName())) {
        TypeMirror typeMirror=element.getSuperclass();
        element = (TypeElement)typeUtils.asElement(typeMirror);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

处理MirroredTypeException

编译器使用注解处理器来处理目标代码时候,目标源码还没有编译成字节码,所以任何指向目标源码字节码的代码都会发生MirroredTypeException。最常见的例子见下面

//已经编译的三方jar:
@Retention(RetentionPolicy.SOURCE)
@interface What{
    Class<?> value()
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
//源码:
@What(A.class)
public class A{}
 
 
  • 1
  • 2
  • 3
//注解处理器
public ProcessorA extends AbstractProcessor{
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        ...
        What what=typeElementA.getAnnotation(What.class);
        Class<?> clazz=what.value();  //这里将有MirroredTypeException
        ...
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注解处理器之所有MirroredTypeException,是因为此时类A还没有被编译成字节码,所以A.class不存在,解决这个问题需要异常,利用异常我能获取类A的名字等信息,但是却得不到A.class,如下

public ProcessorA extends AbstractProcessor{
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        ...
        What what=typeElementA.getAnnotation(What.class);
        try {
            Class<?> clazz=what.value();  //这里将有MirroredTypeException
        }catch(MirroredTypeException mte){
            TypeMirror classTypeMirror =  mte.getTypeMirror();
            TypeElement classTypeElement = (TypeElement)Types.asElement(classTypeMirro);
            //获取canonicalName
            String canonicalName= classTypeElement.getQualifiedName().toString();
            //获取simple name
            String simpleName = classTypeElement.getSimpleName().toString();
        }
        ...
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

转载请注明本文出自远古大钟的博客(http://blog.csdn.net/duo2005duo),谢谢支持!


  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值