二. 重识Java之夯实注解

不忘初心 砥砺前行, Tomorrow Is Another Day !

相关文章

引言

本文概要:

  1. 注解基础知识
  2. 注解的定义及使用

一. 注解的基础知识

注解分类:标准注解、元注解.

1.1 标准注解
  • @Override : 标记覆盖超类的方法
  • @Deprecated : 标记弃用
  • @SuppressWarnings : 取消警告
  • @SafeVarargs : 声明在使用可变长度参数的方法,与泛型类一起使用不会出现类型安全问题
1.2 元注解

简称注解的注解,从而创建新的注解.

  • @Target : 注解所修饰的类型
  • @Inherited : 注解可以被继承
  • @Documented : 标记注解应该被javaDoc工具记录
  • @Retention : 注解的保留策略

这里主要介绍@Target和@Retention两个元注解.

@Target

修饰类型从枚举类ElementType取值

public enum ElementType {
    /** 类,接口(包含注解类型)*/
    TYPE,

    /** 成员变量*/
    FIELD,

    /** 方法*/
    METHOD,

    /** 方法参数或构造方法参数 */
    PARAMETER,

    /** 构造方法  */
    CONSTRUCTOR,

    /** 局部变量 */
    LOCAL_VARIABLE,

    /** 注解类型,可参考元注解Retention声明  */
    ANNOTATION_TYPE,

    /** 包  */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

复制代码
@Retention

保留策略从枚举类RetentionPolicy中取值

public enum RetentionPolicy {
    /**
     * 源码级java文件
     */
    SOURCE,

    /**
     * 编译时class文件  This is the default behavior.
     */
    CLASS,

    /**
     * 
     * 运行时,加载到jvm虚拟机中.
     * 通过反射获取该注解信息
     */
    RUNTIME
}
复制代码

二.注解的定义及使用

  • 定义注解 - 反射机制解析注解 - 使用注解
  • 定义注解 - AbstractProcessor(注解解析器)解析注解 - 使用注解

2.1 运行时注解示例

//定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//运行时注解
public @interface Name {
    //定义成员变量,可以设置默认值
    String value() default "我是默认名";
}

//使用注解
public class UseAnnotation {

    @Name(value = "小学僧")
    public String getName() {
        return "什么都没有";
    }

    @Name
    public String getFinalName() {
        return "什么都没有";
    }
}

//解析注解
public class Main {

    public static void main(String[] args) {
        //通过反射处理
        Method[] methods = UseAnnotation.class.getDeclaredMethods();
        for (Method method : methods) {
            Name name = method.getAnnotation(Name.class);
            System.out.println("注解的值:"+name.value());
        }
    }
}

//输出结果
注解的值:小学僧
注解的值:我是默认名
复制代码

定义了一个注解,并且分别在getName与getFinalName方法上使用;由于第二个方法上没有设置value,所以在反射调用时输出的是默认名.

2.2 编译时注解示例

了解编译时注解,需要先了解下Element相关的知识.接下来看Element.

2.2.1 Element

Element位于javax.lang.model.element包下,一个Element代表一个程序元素.
对应关系如下:

package annotationDemo.compile; //PackageElement包

public class TestElement {//TypeElement类
    private int value;//VariableElement变量

    public int getValue() {//ExecutableElement方法
        return value;
    }
}

复制代码

Element的中重要API;

  • getEnclosingElement : 获取一个元素的外部元素.

    • 比如上述成员变量value,以及getValue方法,它们对应VariableElement、ExecutableElement,那么获取到的外部元素则是TestElement类对应的元素.
  • getEnclosedElement : 获取一个元素的内部元素.

    • 同样,TestElement类对应typeElement元素,那么获取到的是VariableElement和ExecutableElement的元素集合.
2.2.2 使用示例

创建一个java-library,命名为:annotations
定义两个注解用来注入int和String类型数据.

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface injectInt {

}

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface injectString {

}
复制代码

再创建一个java-library,命名为:compiler
定义注解处理器,解析注解

//build.gradle文件

dependencies {
    implementation project(':annotations')
    implementation 'com.squareup:javapoet:1.11.1'//java文件生成工具
    implementation 'com.google.auto.service:auto-service:1.0-rc3'
    //注册注解处理,自动在ressources生成META-INF/services/javax.annotation.processing.Processor的文件.文件内容为:自定义的注解处理器的类的全路径.
}

复制代码
@SupportedAnnotationTypes({"com.cjy.lhk_annotations.mylhk,injectInt",
        "com.cjy.lhk_annotations.mylhk.injectString"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@AutoService(Processor.class)
public class injectProcessor extends AbstractProcessor {

    private static final ClassName CONTEXT =
            ClassName.get("android.content", "Context");


    //待生成java文件的的集合,key为被注解的类的类名,value为GenerateJavaFile对象
    private HashMap<String, GenerateJavaFile> mGenerateJavaFiles = new HashMap<>();


    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        for (TypeElement typeElement : set) {//遍历所有注解类对应的TypeElement
            //获取注解类对应被注解的对应元素
            //比如注解被用在某个成员变量上,那么这个就是获取成员变量对应的元素
            for (Element element : roundEnvironment.getElementsAnnotatedWith(typeElement)) {
                addElementToGenerateJavaFile(element);
            }
        }
        createJavaFile();
        return true;
    }

    /**
     * 解析Element,并添加一个注解元素到对应的GenerateJavaFile对象中
     * (收集信息)
     *
     * @param element 注解元素
     */
    private void addElementToGenerateJavaFile(Element element) {
        //获取element对应成员变量所在的类,即被注解的类
        TypeElement typeElement = (TypeElement) element.getEnclosingElement();//获取外部元素
        //System.out.println("---getQualifiedName =---" + typeElement.getQualifiedName());
        String[] split = typeElement.getQualifiedName().toString().split("\\.");
        String className = split[split.length - 1];

        //通过父类的processingEnv获取报信者,用于在编译过程中打印log
        Messager messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE, "add element to generate file " + className);

        //获取被注解类对应的GenerateJavaFile对象,如果没有,则创建
        GenerateJavaFile generateJavaFile = mGenerateJavaFiles.get(className);
        if (generateJavaFile == null) {
            GenerateJavaFile file = new GenerateJavaFile();
            //设置待生成java文件的包名
            file.packageName = processingEnv.getElementUtils().getPackageOf(element).toString();
            //设置待生成java文件的类名
            file.className = className + "_Inject";
            //初始化元素集合
            file.elements = new ArrayList<>();
            file.elements.add(element);
            //保存被注解类所对应要生成java类的GenerateJavaFile对象
            mGenerateJavaFiles.put(className, file);
        } else {
            //将注解元素添加到有的generateJavaFile对象中
            generateJavaFile.elements.add(element);
        }
    }

    /**
     * 生成java文件
     */
    private void createJavaFile() {
        //遍历GenerateJavaFile集合
        for (String className : mGenerateJavaFiles.keySet()) {

            //获取一个GenerateJavaFile对象
            GenerateJavaFile file = mGenerateJavaFiles.get(className);

            //构建一个构造方法,该构造方法带有一个Context类型的参数
            MethodSpec.Builder builder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(CONTEXT, "context");

            //遍历该类中需要处理的注解元素
            for (Element element : file.elements) {
                //如果注解的成员变量是一个int类型
                if (element.asType().toString().equals("int")) {
                    //在构造方法中添加一条语句
                    //例如:((MainActivity)context).one = context.getResources().getInteger(R.integer.one);
                    builder.addStatement("(($N)context).$N = context.getResources().getInteger(R.integer.$N)",
                            file.className.split("_")[0], element.getSimpleName(), element.getSimpleName());
                    //如果注解的是一个String类型
                } else if (element.asType().toString().equals("java.lang.String")) {
                    //在构造方法中添加一条语句
                    //例如:((MainActivity)context).hello = context.getResources().getString(R.string.hello);
                    builder.addStatement("(($N)context).$N = context.getResources().getString(R.string.$N)",
                            file.className.split("_")[0], element.getSimpleName(), element.getSimpleName());
                }
            }
            //构建一个类,添加一个上述的构造方法
            TypeSpec typeSpec = TypeSpec.classBuilder(file.className)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(builder.build())
                    .build();
            try {
                //输出java文件
                JavaFile javaFile = JavaFile.builder(file.packageName, typeSpec).build();
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 待生成的Java文件信息
     */
    private static class GenerateJavaFile {
        String packageName;//包名
        String className;//类名
        List<Element> elements;//程序元素集合
    }
}

复制代码

注解处理器大致工作流程:

  1. 收集信息
    1. 遍历所有注解类对应的TypeElement
      1. 通过TypeElement获取被注解的元素(如类,成员变量,方法对应的元素对象)
      2. 解析Element(被注解对应元素)信息,将被相关信息添加到待生成的文件对象中
  2. 生成java文件
apt与annotationProcessor的作用

对于compiler项目,我们只需在编译时使用,运行时无需加载到jvm虚拟机中.所以采用apt的替代品annotationProcessor进行引入.

//android项目
dependencies {
    implementation project(':annotations')
    //对于android项目,由于插件的默认支持可以直接使用此方式,无需应用apt插件
    annotationProcessor (:'compiler')

}
复制代码

由于本人技术有限,如有错误的地方,麻烦大家给我提出来,本人不胜感激,大家一起学习进步.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值