Java可以通过@Interface形式定义一个注解,用于修饰包定义、类、构造器、方法、成员、参数、局部变量等,并通过反射获取注解中的信息!
1. 元注解:
Java注解的定义,需要使用元注解修饰(元注解本身也是ElementType.ANNOTATION_TYPE类型的注解)!格式如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
1.1 @Retention元注解:
@Retention元注解有一个RetentionPolicy枚举类型的value成员,用于指示被修饰的注解保留时长!
- RetentionPolicy.SOURCE:指示该注解为编译期注解,仅仅在编译期间使用,编译后丢失;程序运行时期不可通过反射获取!
- RetentionPolicy.CLASS:指示该注解只记录在class文件中,仅仅在类加载的时候使用,Class对象中丢失;程序运行时期不可用通过反射获取;该值为value的默认值!
- RetentionPolicy.RUNTIME:指示该注解为运行期注解,在程序运行期间可以通过反射获取注解信息!
1.2 @Target元注解:
@Target元注解有一个String类型的value成员,用于指示注解修饰那种成员类型!
- ElementType.ANNOTATION_TYPE:修饰注解类型
- ElementType.CONSTRUCTOR:修饰构造器
- ElementType.LOCAL_VARIABLE:修饰局部变量
- ElementType.PACKAGE:修饰包定义
- ElementType.PARAMETER:修饰方法参数
- ElementType.METHOD:修饰方法
- ElementType.FIELD:修饰成员
- ElementType.TYPE:修饰类、接口、或者枚举定义
- ElementType.TYPE_PARAMETER:修饰泛型类的泛型定义
public class AnnotationTypeParameter<@TestTypeParam T> {}
- ElementType.TYPE_USE:修饰类型使用的地方;Java8新增类型,Java8前注解使用的位置只能在类型定义处,引入TPYE_USE类型后,注解可以在任何类型使用的地方!
List<@Test Comparable> list1 = new ArrayList<>();
List<? extends Comparable> list2 = new ArrayList<@Test Comparable>();
@Test String text;
text = (@Test String)new Object();
java.util. @Test Scanner console;
console = new java.util.@Test Scanner(System.in);
1.3 @Documented元注解:
被@Documented元注解修饰的注解,将被javadoc工具提取到被修饰类的API文档中!
1.4 @Inherited元注解:
被@Inherited元注解修饰的注解,将具有继承性,父类被具有@Inherited元注解修饰的注解修饰后,子类自动被该注解修饰!
1.5 @Repeatable元注解:
Java8之前,如果想用同一个注解重复修饰的时候,只能定义一个注解数组,如下:
//AnnotationTest注解数组定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AnnotationTests {
AnnotationTest[] a();
}
//AnnotationTest注解定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AnnotationTest {
/*给name成员,赋初始值为"hello"*/
String name() default "hello";
/*定义num成员,未赋初始值*/
String value();
}
//AnnotationTest注解使用
class Atest
{
@AnnotationTests(a = {@AnnotationTest("test1"),@AnnotationTest("test2")})
public void fun()
{
System.out.println("fun()");
}
}
Java8提供了一个@Repeatable元注解,对上述语法进行了简化:
//AnnotationTest注解定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Repeatable(AnnotationTests.class)
public @interface AnnotationTest {
/*给name成员,赋初始值为"hello"*/
String name() default "hello";
/*定义num成员,未赋初始值*/
String value();
}
//AnnotationTests注解定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AnnotationTests {
/*当使用@Repeatable(AnnotationTests.class)的时候,AnnotationTests中必须有value成员*/
AnnotationTest[] value();
}
//AnnotationTest注解使用
class Atest
{
@AnnotationTest("test1")
@AnnotationTest("test2")
public void fun()
{
System.out.println("fun()");
}
}
PS:Java8后,还提供了针对于重复注解信息提取的反射接口!
2. 自定义注解
注解定义,代码示例:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AnnotationTest {
/*给name成员,赋初始值为"hello"*/
String name() default "hello";
/*定义num成员,未赋初始值*/
String value();
}
Ps:注解中有一个元素名为value的成员,当给注解成员赋值的时候,可以将(value="XXX")简化为("XXX")
注解信息获取,代码示例:
public class Main
{
public static void main(String[] args)
{
/*通过反射结合注解调用Atest中的方法*/
Atest a = new Atest();
/*遍历Atest中方法,结合注解配置进行区别处理*/
Method[] m = Atest.class.getMethods();
for (int i = 0; i < m.length; i++)
{
if (m[i].isAnnotationPresent(AnnotationTest.class))
{
if ("Test".equals(m[i].getAnnotation(AnnotationTest.class).value()))
{
System.out.println(m[i].getAnnotation(AnnotationTest.class).name());
try
{
m[i].invoke(a);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
}
}
class Atest
{
@AnnotationTest("Test")
public void fun1()
{
System.out.println("fun1()");
}
@AnnotationTest(value = "NoTest", name = "test")
public void fun2()
{
System.out.println("fun2()");
}
}
Ps:通过反射获取注解信息已经在<JavaSE学习笔记(14.Java之反射机制)>中具体阐述过了,这里补充一点Class、Constructor、Field、Method、Package等类都是AnnotatedElement接口的实现类,AnnotatedElement接口提供了所有注解信息获取的方法能力!
3. 编译期处理注解:
JSR 269中提供了一组API用于专门编译期处理注解,相当于一组可以插入Java编译流程的插件!并再结合JSR 175 Java元数据规范可以实现类似Lombok一样在编译期间根据注解,生成Java代码的编译期注解代码生成插件!
实现流程:
1. 继承javax.annotation.processing.AbstractProcessor类,并实现process方法(方法内部为注解处理逻辑);
2. 使用@SupportedAnnotationTypes注解指定该Processor类具体处理那个注解;
3. 使用@SupportedSourceVersion注解指定编译版本;
4. 通过Javac命令中 -processor参数指定Processor类
5. 或者在META-INF/services/javax.annotation.processing.Processor中增加Processor类
6. 或者通过Maven Plugin配置Processor类
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<annotationProcessors>
<annotationProcessor>
XXXXXProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
Ps:值得注意的是,在编译期间处理注解的时候,自定义的Processor类不能是java文件,需要将Processor类编译成为class文件,否则在编译期间会报Class not found的错误!
推荐一篇Pluggable Annotation Processing API描述比较细致的文章<Java奇技淫巧-插件化注解处理API(Pluggable Annotation Processing API)>
4. 常用注解:
java.lang包中提供了几个常用注解:
4.1 @Override:
编译期注解,修饰方法,通过编译约束子类中必须重写该方法!
4.2 @Deprecated:
运行期注解,可修饰所有类型,用于修饰已经过时的成员;当已过时成员被调用的时候,编译器会输出编译告警!
4.3 @SuppressWarnings:
编译期注解,抑制编译器告警,@SuppressWarnings("unchecked")
4.4 @SafeVarargs:
运行期注解,针对泛型的类型擦除引发的编译告警,提供的抑制该告警的注解!
Ps:泛型的类型擦除引发的编译告警,可以使用@SafeVarargs消除、@SuppressWarnings("unchecked")消除、也可以在Javac命令增加-Xlint:varargs告警屏蔽项!
4.5 @FunctionalInterface:
运行期注解,修饰接口为函数式接口,可以与lamda表达式组合使用,详细见<JavaSE学习笔记(7.Lamda表达式)>