5

深入理解Java系列—第二篇Annotation

 3 years ago
source link: http://www.demanmath.com/index.php/2020/10/21/shenrulijiejavaxiliedierpianannotation/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

深入理解Java系列—第二篇Annotation

三个类Annotation,ElementType,RetentionPolicy
Annotation注解的接口,所有的注解都继承这个接口。
ElementType 注解的作用范围,类,方法,属性。
RetentionPolicy:编译器和JVM的注解行为

Annotation

public interface Annotation {
    boolean equals(Object var1);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

“每1个Annotation” 都与 “1个RetentionPolicy”关联,并且与 “1~n个ElementType”关联。可以通俗的理解为:每1个Annotation对象,都会有唯一的RetentionPolicy属性;至于ElementType属性,则有1~n个。
RetentionPolicy是编译器和JVM的行为,所以只有唯一的一种。
ElementType是作用范围,可以有很多个场景,1~n个。

ElementType

public enum ElementType {
TYPE, //类,接口,enum
FIELD, //属性
METHOD, //方法
PARAMETER, //参数
CONSTRUCTOR, //构造方法
LOCAL_VARIABLE, //局部变量
ANNOTATION_TYPE, //注解类型声明
PACKAGE, //包声明
TYPE_PARAMETER, //since 1.8 标注类型参数
TYPE_USE; //since 1.8 标注任何类型名称

private ElementType() {
}

}
“每1个Annotation” 都与 “1~n个ElementType”关联。当Annotation与某个ElementType关联时,就意味着:Annotation有了某种用途。
例如,若一个Annotation对象是METHOD类型,则该Annotation只能用来修饰方法。

RetentionPolicy

public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME;

    private RetentionPolicy() {
    }
}

“每1个Annotation” 都与 “1个RetentionPolicy”关联。
a) 若Annotation的类型为 SOURCE,则意味着:Annotation仅存在于编译器处理期间,编译器处理完之后,该Annotation就没用了。
例如,“ @Override ”标志就是一个Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,“@Override”就没有任何作用了。
b) 若Annotation的类型为 CLASS,则意味着:编译器将Annotation存储于类对应的.class文件中,它是Annotation的默认行为。
c) 若Annotation的类型为 RUNTIME,则意味着:编译器将Annotation存储于class文件中,并且可由JVM读入。

插入式注解器

继承与AbstractProcessor,具体实现如下。
关键是要配置:src/main/resources/META-INF/services/javax.annotation.processing.Processor

com.demanmath.libannotationprocessor.CheckBlankProcessor
package com.demanmath.libannotationprocessor;

import com.demanmath.libannotaion.CheckBlank;
import com.google.auto.service.AutoService;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
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.lang.model.util.ElementScanner6;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;

/**
 * @author DemanMath
 * @date 2020/10/16
 */
@SupportedAnnotationTypes("com.demanmath.libannotaion.CheckBlank")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@AutoService(Processor.class)
public class CheckBlankProcessor extends AbstractProcessor {

    private Messager messager;
    private Elements elementUtils;
    private Filer filer;
    private ParamsCheckScan paramsCheckScan;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        elementUtils = processingEnvironment.getElementUtils();
        filer = processingEnvironment.getFiler();
        paramsCheckScan = new ParamsCheckScan();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//        messager.printMessage(Diagnostic.Kind.NOTE,"processor start\n");
        if (!roundEnvironment.processingOver()) {
            for (Element element : roundEnvironment.getRootElements())
                checkNames(element);
        }
        return false;
    }

    private void checkNames(Element element){
        paramsCheckScan.scan(element);
    }

    public class ParamsCheckScan extends ElementScanner6<Void,Void>{

        @Override
        public Void visitVariable(VariableElement variableElement, Void aVoid) {
            if(variableElement.getAnnotation(CheckBlank.class)!=null){
                messager.printMessage(Diagnostic.Kind.NOTE,"find CheckBlank Annotation",variableElement);
                //检查参数是否为空
                if(variableElement.getKind() == ElementKind.PARAMETER){
                    Object o = variableElement.getConstantValue();
                    String name = variableElement.getSimpleName().toString();

                    if(o instanceof String){
                        if(o == null|| ((String) o).isEmpty()){
                            messager.printMessage(Diagnostic.Kind.ERROR,String.format("find parameter %s empty here",name),variableElement);
                            return null;
                        }
                    }else{
                        if(o == null){
                            messager.printMessage(Diagnostic.Kind.ERROR,String.format("find parameter %s null here",name),variableElement);
                            return null;
                        }
                    }
                }
            }
            return super.visitVariable(variableElement, aVoid);
        }
    }
}

自定义注解

@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.PARAMETER,ElementType.FIELD,ElementType.METHOD})
public @interface CheckBlank {
    String value() default "";
}

注解在JVM的自动加载过程。

注解处理分为2种case,第一种是反射的方式,通过获取相关信息处理,这种方式简单,但是性能不行。
第二种方式就是通过JVM自动在编译或者运行的时候,处理。插入式注解器就是这一种。代码定义就是上面,下面讲解配置的情况。
创建一个单独的module: libannotationprocessor
关键是配置:在libannotationprocessor/src/main/resources/META-INF/services/下面创建文件名:javax.annotation.processing.Processor
文件内容就是processor的全名:com.demanmath.libannotationprocessor.CheckBlankProcessor
这样在JVM编译的时候,它会自动运行这个注解插入器。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK