注解

一、概念

注解是Java语言的特性之一,它是在源代码中插入标签,这些标签在后面的编译或者运行过程中起到某种作用,每个注解都必须通过注解接口@interface进行声明,接口的方法对应着注解的元素。

注解的属性也叫做成员变量,注解只有成员变量,没有方法。注解的成员变量在注解的定义中以无形参的方法形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型,注解可以有默认值,需要用default关键字指定。

//定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Coder {
    public String name() default "Unknown";
    public String language() default "Unknown";
    public String company() default "Unknown";
}

//使用
@Coder(name = "Tomy", language = "Java", company = "Galaxy")
public class SuperMan {
}

如果注解内只有一个名为value的属性,应用该属性时可以将值直接写到括号内,不用写 value = "..."。

//定义
public @interface Coder {
    String value();
}

//使用,方法一
@Coder("Tomy")
public class SuperMan {
}

//使用,方法二
@Coder(value = "Tomy")
public class SuperMan {
}

二、标准注解

Java API中默认定义的注解称为标准注解。它们定义在java.lang、java.lang.annotation和javax.annotation包中。

1.编译相关的注解

编译相关的注解是给编译器使用的。
@Override:用于修饰此方法覆盖了父类的方法;
@Deprecated:用于修饰已经过时的属性、方法;
@SuppressWarnnings:用于抑制某种类型的警告;
@SafeVarargs:用于方法和构造函数,用来断言不定长参数可以安全使用;
@Generated:用来表示这段代码不是开发者手动编写的,而是工具生成的;
@FunctionalInterface:表示对应的接口是带单个方法的函数式接口。

2.资源相关的注解

一般用在JavaEE领域。
@PostConstruct:用在控制对象生命周期的环境中,例如Web容器和应用服务器,表示在构造函数之后应该立即调用被该注解修饰的方法;
@PreDestroy:表示在删除一个被注入的对象之前应该立即调用被该注解修饰的方法;
@Resource:用于Web容器的资源注入,表示单个资源;
@Resources:用于Web容器的资源注入,表示一个资源数组。

3.元注解

元注解是用来定义和实现注解的注解,总共有五种:
@Target:用来指定注解的使用范围。

public enum ElementType {
    TYPE,                   //用于描述类、接口(包括注解类型) 或enum声明
    FIELD,                  //用于描述属性
    METHOD,                 //用于描述方法
    PARAMETER,              //用于描述参数
    CONSTRUCTOR,            //用于描述构造函数
    LOCAL_VARIABLE,         //用于描述局部变量
    ANNOTATION_TYPE,        //用于描述注解
    PACKAGE,                //用于包
    TYPE_PARAMETER,         //描述类型,如泛型,String类型
    TYPE_USE;               //描述这个注解可以用在类型的声明式前

    private ElementType() {
    }
}

@Retention:用来指定注解的生命周期。

public enum RetentionPolicy {
    SOURCE,     //只在源码中存在,不存在编译后的.class 文件      
    CLASS,      //默认配置,存在于源码,且编译后也存在.class中,但信息不会被加载到JVM虚拟机中
    RUNTIME;    //源码、class文件、虚拟机中都存在

    private RetentionPolicy() {
    }
}

@Documented:表示被修饰的注解应该被包含在被注解项的文档中(例如用JavaDoc生成的文档)。
@Inherited:表示该注解可以被子类继承。
@Repeatable:表示这个注解可以在同一个项上面应用多次,不过这个注解是在Java 8中才引入的,前面四个元注解都是在Java 5中就已经引入的。

例子:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyService {
    String value() default "";
}
@MyService
public class OneService {
}

public class TwoService extends OneService {
}
@interface Persons {
    Person[] value(); //Persons必须有一个value属性,且属性类型Person必须被@Repeatable注解
}

@Repeatable(Persons.class)
@interface Person {
    String role() default "";
}

@Person(role = "Coder")
@Person(role = "PM")
@Person(role="Artist")
public class SuperMan {
}

三、自定义注解

自定义注解类编写的规则:
1.注解类型定义为 @interface,所有的注解会自动继承 java.lang.Annotation 这一接口,而且不能再去继承其他的类或接口;
2.参数成员只能用 public 或 default 两个关键字修饰;
3.参数成员只能用基本类型:byte, short, char, int, long, float, double, boolean,以及 String, Enum, Class, Annotations 等数据类型,以及这些类型的数组;
4.要获取类方法和字段的注解信息,必须通过 Java 的反射技术;
5.注解也可以不定义成员变量,但这样的注解没有什么作用;
6.自定义注解需要使用元注解进行编写。

自定义注解之源码注解(RetentionPolicy.SOURCE)

源码注解(RetentionPolicy.SOURCE)的生命周期只存在Java源文件这一阶段,是三种生命周期中最短的注解。当在Java源程序上加了一个注解,这个Java源程序要由javac去编译,javac把java源文件编译成.class文件,在编译成class时会把Java源程序上的源码注解给去掉。需要注意的是,在编译器处理期间源码注解还存在,即注解处理器Processor也能处理源码注解,编译器处理完之后就没有该注解信息了。

//定义注解
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.PARAMETER)
@IntDef({Constant.STATUS_OPEN, Constant.STATUS_CLOSE})
public @interface Status { 
}

//使用注解
public class TestStatus {
    private int status;

    public void setStatus(@Status int status) {
        this.status = status;
    }

    public int getStatus() {
        return status;
    }
}

//测试代码
TestStatus testStatus = new TestStatus();
//testStatus.setStatus(1); //源码报错,只能设置@Status注解定义的项目
testStatus.setStatus(Constant.STATUS_OPEN);

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

编译时注解能够自动处理Java源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西,这些操作是通过注解处理器完成的。

使用AndroidStudio自定义编译时注解步骤如下:
步骤1.创建一个Java Library,其gradle配置如下:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.auto.service:auto-service:1.0-rc3' //添加依赖
}

//Error:(23, 35) 错误: 编码GBK的不可映射字符
tasks.withType(JavaCompile){
    options.encoding ="UTF-8"
}

sourceCompatibility = "7"
targetCompatibility = "7"

步骤2.定义编译时注解

package com.tomorrow.testannotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
}

步骤3.定义编译时注解处理器

package com.tomorrow.testannotation;

import com.google.auto.service.AutoService;

import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        System.out.println("zwm, annotation process start");
        Set<? extends Element> elements = env.getElementsAnnotatedWith(MyAnnotation.class);
        Messager messager = processingEnv.getMessager();
        for (Element  element : elements) {
            messager.printMessage(Diagnostic.Kind.NOTE, "zwm, output: " + element);
        }
        return true;
    }

    //指定处理的版本,这里返回最新版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        SourceVersion sourceVersion = SourceVersion.latestSupported();
        String name = sourceVersion.name();
        System.out.println("zwm, getSupportedSourceVersion: " + name);
        return SourceVersion.latestSupported();
    }

    //给到需要处理的注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            String canonicalName = annotation.getCanonicalName();
            System.out.println("zwm, canonicalName: " + canonicalName);
            types.add(canonicalName);
        }
        return types;
    }

    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
        annotations.add(MyAnnotation.class); //添加编译时注解处理器需要支持的编译时注解
        return annotations;
    }
}

步骤4.在META-INF目录中标识编译时注解处理器
首先需要创建一个resources目录(与src目录同级),然后在里面创建META-INF/services/javax.annotation.processing.Processor文件,在这个文件中写上注解处理器类的完整路径。

com.tomorrow.testannotation.MyProcessor

步骤5.对于使用该编译时注解的module,其gradle配置如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.tomorrow.test2019_1"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        javaCompileOptions { //配置javaCompileOptions
            annotationProcessorOptions {
                includeCompileClasspath = true
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions { //配置compileOptions
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    implementation project(path: ':TestAnnotation') //添加依赖,settings.gradle配置为:include ':app', ':TestAnnotation'
}

步骤6.使用编译时注解

package com.tomorrow.test2019_1;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.tomorrow.testannotation.MyAnnotation;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @MyAnnotation
    public String testAnnotation(int id) {
        return String.valueOf(id);
    }
}

步骤7.编译Project,在Gradle Console窗口中可以看到以下输出:

zwm, getSupportedSourceVersion: RELEASE_8
zwm, canonicalName: com.tomorrow.testannotation.MyAnnotation
zwm, annotation process start
注: zwm, output: testAnnotation(int)
zwm, annotation process start

自定义注解之运行时注解(RetentionPolicy.RUNTIME)

运行时注解一般和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简单。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName { //水果名称注解定义
    String value() default "";
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor { //水果颜色注解定义
    enum Color{ BLUE,RED,GREEN};

    Color fruitColor() default Color.GREEN;
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider { //水果供应商注解定义
    String providerId() default "";
    String providerName() default "";
}

//注解处理器
public class FruitInfoUtil {
    private static final String TAG = "FruitInfoUtil";

    public static void getFruitInfo(Class<?> clazz){
        String strFruitName = "水果名称:";
        String strFruitColor = "水果颜色:";
        String strFruitProvider = "供应商信息:";

        Field[] fields = clazz.getDeclaredFields();

        for(Field field :fields){
            if(field.isAnnotationPresent(FruitName.class)){
                FruitName fruitName = field.getAnnotation(FruitName.class);
                strFruitName = strFruitName + fruitName.value();
                Log.d(TAG, "zwm, " + strFruitName);
            } else if(field.isAnnotationPresent(FruitColor.class)){
                FruitColor fruitColor = field.getAnnotation(FruitColor.class);
                strFruitColor = strFruitColor + fruitColor.fruitColor().toString();
                Log.d(TAG, "zwm, " + strFruitColor);
            } else if(field.isAnnotationPresent(FruitProvider.class)){
                FruitProvider fruitProvider= field.getAnnotation(FruitProvider.class);
                strFruitProvider = strFruitProvider + "id:" + fruitProvider.providerId() + ",name: " + fruitProvider.providerName();
                Log.d(TAG, "zwm, " + strFruitProvider);
            }
        }
    }
}

//注解使用
public class Fruit {
    @FruitName("Apple")
    private String fruitName;

    @FruitColor(fruitColor = FruitColor.Color.RED)
    private String fruitColor;

    @FruitProvider(providerId="1", providerName="陕西红富士集团")
    private String fruitProvider;

    public void setFruitName(String fruitName) {
        this.fruitName = fruitName;
    }
    public String getFruitName() {
        return fruitName;
    }

    public void setFruitColor(String fruitColor) {
        this.fruitColor = fruitColor;
    }
    public String getFruitColor() {
        return fruitColor;
    }

    public void setFruitProvider(String fruitProvider) {
        this.fruitProvider = fruitProvider;
    }
    public String getFruitProvider() {
        return fruitProvider;
    }
}

//测试代码
FruitInfoUtil.getFruitInfo(Fruit.class);

//输出
02-24 17:22:54.584 zwm, 水果颜色:RED
02-24 17:22:54.586 zwm, 水果名称:Apple
02-24 17:22:54.589 zwm, 供应商信息:id:1,name: 陕西红富士集团
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容