一、概念
注解是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: 陕西红富士集团