一、注解(Annotation)用途
注解本身不起直接作用,而是起到信息提示的作用
- 提供信息给编译器/IDE工具
- 可用于其他工具来产生额外的代码/配置文件等
- 有些注解可在程序运行时访问,增加程序的动态性(在运行时通过反射来修改注解)
二、java预定义注解
1.普通注解
eg:
@Override 表示继承和复写
@SuppressWarnings 表示压制警告
2.元注解
修饰注解的注解
@Target 设置注解目标范围
@Retention 设置保持性
@Documented 文档
@Inherited 注解继承
@Repetable此注解可以重复修饰
2.1@Retention(保留)
用于修饰其他注解的存在范围
eg: @Retention(RetentionPolicy.RUNTIME)
-
RetentionPolicy.SOURCE 注解仅存在源码, 不在.class文件。通常用于IDE做源码质量分析和监控,eg:@Override
-
RetentionPolicy.CLASS (默认的注解保留策略) 注解存在于.class文件,但是不能被JVM加载
-
RetentionPolicy.RUNTIME 注解可以被JVM运行时访问到。通常情况下可以结合反射来做一些事
2.2@Inherited
普通注解没有继承功能,@Inherited让一个类和它的子类都包含某个注解
2.3@Repetable
自JDK1.8引入,表示被修饰的注解可以重复应用标注,需要定义注解和容器注解
eg:
//注解
@Repeatable(RepeatableAnnotations.class)
public @interface RepeatableAnnotation {
int a() default 0;
int b() default 0;
int c() default 0;
}
//容器注解
public @interface RepeatableAnnotations {
RepeatableAnnotation[] value();
}
public class RepeatableTest {
@RepeatableAnnotation(a=1, b=2, c=3)
@RepeatableAnnotation(a=5, b=2, c=4)
public static void add(int a, int b, int c){
if(a + b < c){
System.out.println("a + b < c");
}
}
}
2.4@Documented
指明这个注解可以被javadoc工具解析,形成帮助文档
三、自定义注解
1.定义注解
注解可以包括的类型
- 8种基本类型(int/short/long/float/double/byte/char/boolean)
- String
- Class
- enum类型
- 注解类型
- 由以上类型组成的数组
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//注解接口
public @interface singleTest {
//注解的成员属性 属性名value 缺省值""
String value() default "";
}
public @interface BugReport
{
enum Status{NOTBUG, FIXED, CONFIRMED, UNCONFIRMED};
boolean showStopper() default true;
Status status() default Status.UNCONFIRMED;
String[] reportedBy();
}
2.使用注解
//定义
public @interface SingleValue{
int value() default 0;
}
//使用
@SingleValue
@SingleValue(5)
@SingleValue(value=5)
//定义
public @interface MutipleValues{
int a() default 0;
int b() default 0;
}
//使用
@MutipleValues
@MutipleValues(a=1)
@MutipleValues(a=1, b=2)
@MutipleValues(b=2, a=1)
//错误写法
@MutipleValues(1, 2)
3.注解使用的位置
eg: @Retention(RetentionPolicy.RUNTIME) //表示该注解会保留在class文件中,并能被JVM加载
@Target(ElementType.METHOD) //表示该注解只能用于方法
@Target可以限定位置
允许的位置
-
包
-
类
-
接口
-
方法
-
成员变量
-
构造器
-
局部变量/形参变量/类型参数
4.自定义注解实例
Test.java 定义注解
package org.xll.HelloWorld.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
Foo.java 使用注解
package org.xll.HelloWorld.test;
public class Foo {
@Test
public static void m1(){
}
public static void m2(){
}
@Test
public static void m3(){
throw new RuntimeException("Boom");
}
public static void m4(){
}
@Test
public static void m5(){
}
public static void m6(){
}
@Test
public static void m7(){
throw new RuntimeException("Crash");
}
public static void m8(){
}
}
Main.java 通过反射判断加注解的方法
package org.xll.HelloWorld.test;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception{
int passed = 0, failed = 0;
String className = "org.xll.HelloWorld.test.Foo";
//通过反射获取 Foo 所有方法
for(Method method: Class.forName(className).getMethods()){
//如果方法标有 @Test 注解
if(method.isAnnotationPresent(Test.class)) {
try {
//调用此方法
method.invoke(null);
passed++;
} catch (Throwable ex) {
System.out.println("Test " + method.getName() + " failed: " + ex.getCause());
failed++;
}
}
}
System.out.printf("Passed: %d, Failed %d%n", passed, failed);
}
}
运行结果
Test m3 failed: java.lang.RuntimeException: Boom
Test m7 failed: java.lang.RuntimeException: Crash
Passed: 2, Failed 2
四、RUNTIME注解的实现本质
eg:
Fruit.java 定义注解
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Fruit {
String name() default "";
}
RuntimeMain.java 使用注解
@Fruit(name = "Apple")
public class RuntimeMain {
public static void main(String[] args) {
Fruit fruit = RuntimeMain.class.getAnnotation(Fruit.class);
System.out.println(fruit.name());
//Apple
System.out.println(fruit.getClass().getName());
//com.sun.proxy.$Proxy1 (代理类型)
System.out.println(fruit.getClass().getGenericInterfaces()[0]);
//interface org.xll.HelloWorld.runtimeTest.Fruit
InvocationHandler h = Proxy.getInvocationHandler(fruit);
System.out.println(h.getClass().getName());
//sun.reflect.annotation.AnnotationInvocationHandler
}
}
将Fruit.class反编译,可以发现注解定义 @interface 在字节码中是以 interface 存在的
RUNTIME注解调用路线
- 注解采用接口中的方法来表示变量
- java为注解产生一个代理类。这个代理类包括一个AnnotationInvocationHandle成员变量
- AnnotationInvocationHandle有一个Map的成员变量,用于存储所有的注解的属性赋值
- 在程序中,调用注解接口的方法,将会被代理类接管,然后根据方法的名字,到Map中拿相应的Value并返回
RUNTIME注解设计思路
- 传统接口中的成员变量,都是 public final static
- 注解需要随意赋值
- 注解方法表示变量
- 采用代理类拦截注解方法访问
- 所有的注解的赋值,都放在Map中,访问速度快