1 注解概述
- Annontation 是 Java5 开始引入的新特征,中文名称叫注解;
- 注解是插入到代码中的一种注释或者说是一种元数据;
- 这些注解信息可以在编译期使用编译工具进行处理,也可以在运行期使用 Java 反射机制进行处理;
2 注解的用处
- 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等;
- 跟踪代码依赖性,实现替代配置文件功能。如Spring中@Autowired;
- 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;
3 注解的原理
- 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类;
- 我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1;
- 通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法,该方法会从memberValues这个Map中索引出对应的值, 而memberValues的来源是Java常量池;
4 元注解
java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
注解 | 说明 |
---|---|
@Documented | 是否将注解包含在JavaDoc中 |
@Retention | 什么时候使用该注解 |
@Target | 注解用于什么地方 |
@Inherited | 是否允许子类继承该注解 |
- @Documented:一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中;
- @Retention:定义该注解的生命周期:
- RetentionPolicy.SOURCE : 在编译阶段丢弃。 这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码, @Override, @SuppressWarnings都属于这类注解;
- RetentionPolicy.CLASS : 在类加载的时候丢弃。 在字节码文件的处理中有用,注解默认使用这种方式;
- RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解, 因此可以使用反射机制读取该注解的信息,我们自定义的注解通常使用这种方式;
- @Target:表示该注解用于什么地方。 默认值为任何元素,可用的ElementType参数包括:
参数 | 说明 |
---|---|
ElementType.CONSTRUCTOR | 用于描述构造器 |
ElementType.FIELD | 成员变量、对象、属性(包括enum实例) |
ElementType.LOCAL_VARIABLE | 用于描述局部变量 |
ElementType.METHOD | 用于描述方法 |
ElementType.PACKAGE | 用于描述包 |
ElementType.PARAMETER | 用于描述参数 |
ElementType.TYPE | 用于描述类、接口(包括注解类型)或enum声明 |
- @Inherited:定义该注释和子类的关系。 @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。 如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
5 常见标准的Annotation
1 Override (RetentionPolicy.SOURCE : 在编译阶段丢弃):
Override是一个标记类型注解,它被用作标注方法。 它说明了被标注的方法重载了父类的方法,起到了断言的作用。 如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。
2 Deprecated:
Deprecated也是一种标记类型注解。 当一个类型或者类型成员使用@Deprecated修饰时,编译器将不鼓励使用这个被标注的程序元素, 所以使用这种修饰具有一定的“延续性”: 如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员, 虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。
3 SuppressWarnings:
SuppressWarning不是一个标记类型注解。 它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。 对于javac编译器来讲,对-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。 @SuppressWarnings(“unchecked”)
6 自定义注解使用的规则
- Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口;
- 参数成员只能用public或默认(default)这两个访问权修饰;
- 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、Annotations等数据类型,以及这一些类型的数组;
- 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取Annotation对象, 因为除此之外没有别的获取注解对象的方法;
- 注解也可以没有定义成员, 不过这样注解就没啥用了;
注意:自定义注解需要使用到元注解
自定义注解示例
1 自定义水果颜色注解
/**
* 水果颜色注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
@interface FruitColor {
/**
* 颜色枚举
*/
public enum Color{绿色,红色,青色};
/**
* 颜色属性 (注意:这里的属性指的就是方法)
*/
Color fruitColor() default Color.绿色;//默认是是绿色的
}
2 自定义水果名称注解
/**
* 水果名称注解
*/
@Target(FIELD) //ElementType.FIELD:成员变量、对象、属性(包括enum实例)
@Retention(RUNTIME)// 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。
@Documented
public @interface FruitName {
public String fruitName() default "";
}
3 水果供应商注解
/**
* 水果供应者注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应者编号
*/
public int id() default -1;
/**
* 供应商名称
*/
public String name() default "";
/**
* 供应商地址
*/
public String address() default "";
}
4 通过反射来获取水果信息
/**
* 通过反射获取水果信息
*/
public class 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 fruitName=field.getAnnotation(FruitName.class);
strFruitName=strFruitName+fruitName.fruitName();
System.out.println(strFruitName);
}else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor=field.getAnnotation(FruitColor.class);
strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider=field.getAnnotation(FruitProvider.class);
strFruitProvider=strFruitProvider
+ "[ 供应商编号:"+fruitProvider.id()
+" 供应商名称:" +fruitProvider.name()
+" 供应商地址:"+fruitProvider.address()+"]";
System.out.println(strFruitProvider);
}
}
}
}
5 使用注解初始化实例类
/**
* 定义一个实例类
* 这里使用注解来初始化
*/
public class Apple {
@FruitName(fruitName = "苹果")
private String appleName;
@FruitColor(fruitColor = FruitColor.Color.红色)
private String appleColor;
@FruitProvider(id=1,name="红富士",address="陕西省西安市延安路89号红富士大厦")
private String appleProvider;
public String getAppleName() {
return appleName;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleProvider() {
return appleProvider;
}
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
}