java注解之编译时注解RetentionPolicy.CLASS 基本用法

原创 2018年04月15日 11:54:00

1 前言

我们知道,在日常开发中我们常用的两种注解是运行时注解和编译时注解,运行时注解是通过反射来实现注解处理器的,对性能稍微有一点损耗,而编译时注解是在程序编译期间生成相应的代理类,替我们完成某些功能。今天我们来讲解一下编译时注解以及写一个小例子,以便加深对编译时注解的理解。

2 编译时注解

编译时注解(RetentionPolicy.CLASS),指@Retention(RetentionPolicy.CLASS)作用域class字节码上,生命周期只有在编译器间有效。编译时注解注解处理器的实现主要依赖于AbstractProcessor来实现,这个类是在javax.annotation.processing包中,同时为了我们自己生成java源文件方便,我们还需要引入一些第三方库,主要包括
javapoet 用于生成java源文件,可参考https://github.com/square/javapoet
auto-service 主要用于生成一些辅助信息,例如META-INF/services 一些信息等

编译时注解的核心就是实现AbstractProcessor的process()方法,一般来说主要有以下两个步骤
1 搜集信息,包括被注解的类的类信息,方法,字段等信息,还有注解的值
2 生成对应的java源代码,主要根据上一步的信息,生成响应的代码

下面我们来实际的写一个编译时注解的例子

3 编译时注解例子

假设我们写这样一个类注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Bind {

    int value();

}

这个Bind注解只能作用与类上面,一般我们作用于某个类上面了,我们做什么工作呢,我们新建一个类,这个类的内容大体如下:

public class XXX$$value {
  public int sayHello(int n) {
    return value;
  }
}

其中,XXX是被注解的类名,value是注解的值。
看起来很简单,对吧?下面我们就开始吧。

1 新建两个java model,注意不是Android library model
这里写图片描述
model的gradle配置如下

apply plugin: 'java'

为什么要新建两个module呢,
(1) 是因为后面再引用的时候,两者有些不一样,可以看一下

    api project(':ioc-annotation')
    annotationProcessor project(':ioc-compiler')

(2) 因为两者的引用方式不同,导致最好把注解处理器和注解分开,这样我们也能更好的解耦

注意,ioc-compiler需要引用以下两个开源库

apply plugin: 'java'

//解决编译中文乱码问题
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    compile 'com.google.auto.service:auto-service:1.0-rc4'
    compile 'com.squareup:javapoet:1.7.0'
    compile project(':ioc-annotation')
}

sourceCompatibility = "1.8"
targetCompatibility = "1.8"

2 新建Bind注解,指定注解类型为class

/**
 * @author Created by qiyei2015 on 2018/4/14.
 * @version: 1.0
 * @email: 1273482124@qq.com
 * @description: Bind注解 指明注解作用域为类
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Bind {

    int value();

}

这一步挺好理解的。

3 新建BindProcessor继承与AbstractProcessor并实现其process方法

/**
 * @author Created by qiyei2015 on 2018/4/15.
 * @version: 1.0
 * @email: 1273482124@qq.com
 * @description: Bind注解处理器
 */
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor{

    /**
     * java源文件操作相关类,主要用于生成java源文件
     */
    private Filer mFiler;
    /**
     * 注解类型工具类,主要用于后续生成java源文件使用
     * 类为TypeElement,变量为VariableElement,方法为ExecuteableElement
     */
    private Elements mElementsUtils;
    /**
     * 日志打印,类似于log,可用于输出错误信息
     */
    private Messager mMessager;

    private static final ClassName sClassName = ClassName.get("com.qiyei.ioc.api", "Test");

    /**
     * 初始化,主要用于初始化各个变量
     * @param processingEnv
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        mFiler = processingEnv.getFiler();
        mElementsUtils = processingEnv.getElementUtils();
        mMessager = processingEnv.getMessager();
    }

    /**
     * 支持的注解类型
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {

        Set<String> typeSet = new LinkedHashSet<>();

        typeSet.add(Bind.class.getCanonicalName());

        return typeSet;
    }

    /**
     * 支持的版本
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     *
     * 1.搜集信息
     * 2.生成java源文件
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        if (!annotations.isEmpty()){
            //获取Bind注解类型的元素,这里是类类型TypeElement
            Set<? extends Element> bindElement = roundEnv.getElementsAnnotatedWith(Bind.class);

            try {
                generateCode(bindElement);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return true;
        }

        return false;
    }

    /**
     *
     * @param elements
     */
    private void generateCode(Set<? extends Element> elements) throws IOException{

        for (Element element : elements){

            //由于是在类上注解,那么获取TypeElement
            TypeElement typeElement = (TypeElement) element;

            //获取全限定类名
            String className = typeElement.getQualifiedName().toString();

            mMessager.printMessage(Diagnostic.Kind.WARNING,"className:" + className);

            //获取包路径
            PackageElement packageElement = mElementsUtils.getPackageOf(typeElement);
            String packageName = packageElement.getQualifiedName().toString();

            mMessager.printMessage(Diagnostic.Kind.WARNING,"packageName:" + packageName);

            //获取用于生成的类名
            className = getClassName(typeElement,packageName);

            //获取注解值
            Bind bindAnnotation = typeElement.getAnnotation(Bind.class);
            int value = bindAnnotation.value();
            System.out.println("value:" + value);

            //生成方法
            MethodSpec.Builder methodBuilder = MethodSpec
                    .methodBuilder("sayHello")
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(TypeName.INT,"n")
                    .returns(TypeName.INT);

            //$L表示字面量 $T表示类型
            methodBuilder.addStatement("return $L",value);

            //生成的类
            TypeSpec type = TypeSpec
                    .classBuilder(className + "$$" + value)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(methodBuilder.build())
                    .build();

            //创建javaFile文件对象
            JavaFile javaFile = JavaFile.builder(packageName,type).build();
            //写入源文件
            javaFile.writeTo(mFiler);

        }
    }

    /**
     * 根据type和package获取类名
     * @param type
     * @param packageName
     * @return
     */
    private static String getClassName(TypeElement type, String packageName) {
        int packageLen = packageName.length() + 1;
        return type.getQualifiedName().toString().substring(packageLen)
                .replace('.', '$');
    }
}

其他方法暂时不做讲解,逻辑都很简单,主要讲解generateCode(Set

            //获取全限定类名
            String className = typeElement.getQualifiedName().toString();
            //获取包路径
            PackageElement packageElement = mElementsUtils.getPackageOf(typeElement);
            String packageName = packageElement.getQualifiedName().toString();

(2) 构造如上所示的java源文件

            //生成方法
            MethodSpec.Builder methodBuilder = MethodSpec
                    .methodBuilder("sayHello")
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(TypeName.INT,"n")
                    .returns(TypeName.INT);

            //$L表示字面量 $T表示类型
            methodBuilder.addStatement("return $L",value);

            //生成的类
            TypeSpec type = TypeSpec
                    .classBuilder(className + "$$" + value)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(methodBuilder.build())
                    .build();

这里主要是用到javapoet,关于为什么用这个开源库,首先是用法比较简单,其次很多开源框架都在用,包括ButterKnife等。关于生成java代码请一定参考https://github.com/square/javapoet 里面有很详细的说明。

(3) 将java源码写入文件

            //创建javaFile文件对象
            JavaFile javaFile = JavaFile.builder(packageName,type).build();
            //写入源文件
            javaFile.writeTo(mFiler);

(4) 使用Bind注解,然后编译生成代码
我们在我们的一个Activity上使用Bind注解,如下:

/**
 * @author Created by qiyei2015 on 2017/8/28.
 * @version: 1.0
 * @email: 1273482124@qq.com
 * @description:
 */
@Bind(10)
public class MainActivity extends BaseSkinActivity {

    private RecyclerView mRecyclerView;

    private static final int MY_PERMISSIONS_REQUEST_WRITE_STORE = 1;

    /**
     * ViewModel
     */
    private MainMenuViewModel mMenuViewModel;

    private MainMenuAdapter mMenuAdapter;

    /**
     * 标题栏
     */
    private CommonTitleBar mTitleBar = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        PermissionManager.requestAllDangerousPermission(this);
        initData();
        initView();
        LogManager.i(TAG,"onCreate");
    }
    .......

}

然后编译,生成的代码如下:
代码路径:\build\generated\source\apt\baidu\debug\com\qiyei\appdemo\ui\activity

package com.qiyei.appdemo.ui.activity;

public class MainActivity$$10 {
  public int sayHello(int n) {
    return 10;
  }
}

可以看到,符合我们的预期,这样我们一个简单的编译时注解就已经完成了

注:虽然我们的代码如约生成了,但是我们并没有用生成的代码,所以我们还应该写一个api来使用生成的java源代码文件,不过这都是后话了,下次再介绍。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qiyei2009/article/details/79947352

自定义注解之编译时注解(RetentionPolicy.CLASS)(一)

说到编译时注解(RetentionPolicy.CLASS)都要和注解处理器(Annotation Processor)扯上关系,因为这里是真正体现编译时注解价值的地方。需要注意的一点是,运行时注解(...
  • github_35180164
  • github_35180164
  • 2016-08-05 13:54:15
  • 11913

自定义注解之编译时注解(RetentionPolicy.CLASS)(三)—— 常用接口介绍

前面介绍了关于编译时注解的使用方式,这里再补充一个关于注解处理器开发中的一些常用类、接口的使用方式和概念。 Element和TypeMirror 我觉得这两个是开发注解处理器最重要的两个概念,理解这两...
  • github_35180164
  • github_35180164
  • 2016-08-10 17:33:11
  • 4012

Java注解全解析(三)——编译时注解示例

编译时注解示例
  • al4fun
  • al4fun
  • 2016-12-20 17:55:41
  • 467

自定义注解之编译时注解(RetentionPolicy.CLASS)(二)——JavaPoet

在使用编译时注解时,需要在编译期间对注解进行处理,在这里我们没办法影响程序的运行逻辑,但我们可以进行一些需处理,比如生成一些功能性代码来辅助程序的开发,最常见的是生成.java 源文件,并在程序中可以...
  • github_35180164
  • github_35180164
  • 2016-08-07 13:24:59
  • 2896

深入理解编译注解(五)RetentionPolicy.SOURCE 和 RetentionPolicy.CLASS区别讨论

前言这篇我觉得应该是一个讨论篇,因为我自己还没有找到一个非常满意的答案,希望大家一起来讨论。正文元注解RetentionPolicy,表明注解的生命周期: 1、SOURCE:在原文件中有效,被编译...
  • u011315960
  • u011315960
  • 2017-03-22 14:19:06
  • 3358

java 注解——使用详解

在一些强大的第三方框架中我们常常可以见到注解的身影。xUtils、Retrofit等。那么注解到底有什么魅力和好处让我们在设计种种框架的时候用到它呢?对于注解的理解: (仅仅为个人理解) 1,我们...
  • yehui928186846
  • yehui928186846
  • 2016-05-20 16:20:09
  • 5703

编译时注解

  • 2017年10月17日 14:35
  • 4.32MB
  • 下载

java注解处理器(运行时和编译时处理的注解)

对于注解,如果没有注解处理器,其作用和注释没有多大区别。简单的注解处理器示例: 其中FruitName、FruitColor、FruitProvider是三个自定义注解,只有一个元素时可以省略写...
  • honghailiang888
  • honghailiang888
  • 2016-02-04 14:43:07
  • 496

编译时注解(BufferKnife等)与运行时注解(RetentionPolicy.RUNTIME)

  因为运行时注解涉及到反射,所以运行时注解的效率多少会受到影响,现在很多的开源项目使用的是编译时注解。编译时注解(如@Override@Deprecated)和运行时注解(如@Autowired)都...
  • ShareUs
  • ShareUs
  • 2018-03-23 00:06:33
  • 93

Java编译时注解自动生成代码

在开始之前,我们首先申明一个非常重要的问题:我们并不讨论那些在运行时(Runtime)通过反射机制运行处理的注解,而是讨论在编译时(Compile time)处理的注解。注解处理器是一个在javac中...
  • robertcpp
  • robertcpp
  • 2016-06-16 21:31:11
  • 7021
收藏助手
不良信息举报
您举报文章:java注解之编译时注解RetentionPolicy.CLASS 基本用法
举报原因:
原因补充:

(最多只允许输入30个字)