注解概述
- 注解是 JDK1.5 的新特性。
- 标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
- 注解是给编译器或 JVM 看的,编译器或 JVM 可以根据注解来完成对应的功能。
注解的作用:
- 使用javadoc生成帮助文档:里边可以包含注解@author和@version
- 编译检查:@Override @FunctionalInterface
- 框架的配置(框架=代码+配置)
自定义注解
定义没有属性的注解
格式:
public @interface 注解名{ }
注意:
- 注解使用的也是.java文件,编译生成的也是.class文件
- 注解和类和接口和枚举都是同一个层次的,都是一种数据类型
定义有属性的注解
注解中没有成员变量,也没有成员方法
注解中可以包含属性,属性看成和抽象方法一个格式,但是可以包含默认值
定义格式:
public @interface 注解名{
修饰符 数据类型 属性名();
修饰符 数据类型 属性名() default 默认值;
}
注意:
- 注解的属性修饰符省略不写则默认为 public abstract ;建议写出,增强语句的可读性
注解属性的数据类型:
- 基本数据类型(4类8种):byte,short,int,long,float,double,char,boolean
引用数据类型:String类型,反射类型,注解类型,枚举类型
- 以及以上所有类型的一维数组
示例:
public @interface MyAnno02 {
//定义一个int类型的属性
//public abstract int a();
//abstract int a();
int a();
//定义一个double类型的属性,给属性添加默认值8.8
public abstract double d() default 8.8;
//定义一个String类型的数组属性
public abstract String[] arr();
//定义反射类型的属性(了解)
public abstract Class clazz();
//定义注解类型的属性(了解)
public abstract MyAnno01 my01();
//定义枚举类型的属性(了解)
public abstract Color c();
}
定义包含特殊属性 value 的注解
- 注解中只有一个属性,并且叫 value
- 注解中有其他的属性,但是属性必须有默认值
示例
public @interface MyAnno03 {
public abstract String value();
public abstract int aaa() default 10;
}
@AliasFor 注解:声明别名
@AliasFor 是 Spring 框架的一个注解,用于声明注解属性的别名。
它有两种不同的应用场景:
- 注解内的别名
- 元数据的别名
两者主要的区别在于是否在同一个注解内。
注解内的别名
可以使用 @AliasFor 将注解的两个属性互相声明为别名,互为别名的属性使用是等价的。
注意:
- 组成别名对的每个属性都必须加上注释 @AliasFor,且必须使用 attribute() 或 value() 属性引用该别名对中另一个属性
- 别名属性必须声明相同的返回类型
- 别名属性必须声明一个默认值
- 别名属性必须声明相同的默认值
示例:
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogAnnotation {
@AliasFor("methodDesc")
String value();
@AliasFor("value")
String methodDesc() default "";
}
元数据的别名
把多个元注解的属性组合在一起形成新的注解
创建一个 @MyAnnotationB 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@MyAnnotationA
public @interface LogAnnotationB {
@AliasFor(annotation = LogAnnotation.class,value = "methodDesc")
String value() default "";
}
这时 @LogAnnotation(“vvv”) 就和 @LogAnnotationB(methodDesc=“vvv”) 等价。
可以理解成,注解 LogAnnotationB 的 value 属性重写了注解 LogAnnotation 的 methodDesc 属性,注意属性的返回类型必须相同。
SpringBoot这种用法很多,例如最常见的 @Component 和 @Configuration
使用自定义注解
注解可以使用的位置:包上,类上|接口上,成员变量上,成员方法上,构造方法上,局部变量上,方法的的参数上...
注意:
- 同一个位置,同名的注解只能使用一次
- 不同的位置,同名的注解可以多次使用
注解的使用格式:
- 没有属性的注解,通过 @注解名 可以直接使用。例如:@MyAnno01
- 有属性的注解:必须使用键值对的方式,给注解中所有的属性都赋值之后,才能使用注解
格式:
@注解名(属性名=属性值,属性名=属性值,属性名=属性值,...属性名=属性值)
注:
a.有默认值的属性,可以不同赋值,使用默认值
b.给多个属性赋值,中间要使用逗号隔开
c.属性的数据类型是数组,属性的值需要使用 { } 包裹起来,说明这一组值是一个数组的属性值
数组只有一个值,可以省略 { }
示例:arr = {"a","b","c"} arr = {"a"} ==> arr = "a"
d.注解中只有一个属性,并且叫 value;或者注解中有其他的属性,但是属性均有默认值
那么我们在使用注解的时候,给属性赋值,可以省略属性名,直接写属性值
示例:(任意的数据类型)value="aaa" ==> "aaa"
示例
@MyAnno01 // 没有属性的注解
@MyAnno02(a = 10, arr={"aaa","bbb","ccc"}) // 有多个属性的注解
public class UseMyAnno {
@MyAnno01
@MyAnno02(a=100, d=1.1, arr="aaa") // arr属性的数据类型是数组,只有一个值的情况,可省略 {}
private String name;
@MyAnno01
@MyAnno03(value="aaa")
public UseMyAnno() {
}
@MyAnno01
@MyAnno03("www") // 可省略属性名的注解
public UseMyAnno(String name) {
this.name = name;
}
@MyAnno01
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
元注解
元注解:java已经定义好的注解,可以使用元注解修饰自定义的注解
@Target
java.lang.annotation.Target
作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。
属性:
- ElementType[] value :只有一个属性,属性名叫value(使用的时候,就可以省略属性名,直接写属性值)
java.lang.annotation.ElementType:是一个枚举,枚举中的变量都是常量,可以通过枚举名.变量名直接使用
ElementType枚举中的常量:
TYPE, // 类,接口 FIELD, // 成员变量 METHOD, // 成员方法 PARAMETER, // 方法参数 CONSTRUCTOR, // 构造方法 LOCAL_VARIABLE // 局部变量
@Retention
java.lang.annotation.Retention
作用:用来标识注解的生命周期(有效范围)
属性:
- RetentionPolicy value: 只有一个属性,属性名叫value(使用的时候,就可以省略属性名,直接写属性值)
java.lang.annotation.RetentionPolicy:是一个枚举,枚举中的变量都是常量,可以通过枚举名.变量名直接使用
RetentionPolicy枚举中的常量:
SOURCE, // 注解只作用在源码(.java)阶段,生成的字节码文件(.class)中不存在 CLASS, // 注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值 RUNTIME // 注解作用在源码阶段,字节码文件阶段,运行阶段
示例
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//声明自定义注解Book可以使用的位置:类上|接口上,构造方法上,成员变量上
//@Target(value={ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD})
@Target({ElementType.TYPE,ElementType.CONSTRUCTOR,ElementType.FIELD})
//声明自定义注解Book:在.java文件中,在.class文件中和在内存中都有效
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
//书名
public abstract String value();
//价格,默认值为 100
public abstract double price() default 100;
//多位作者
public abstract String[] authors();
}
注解解析
作用:获取注解的属性值
原理:注解的解析底层使用的反射技术
java.lang.reflect.AnnotatedElement接口:在接口中定义了注解解析的方法
AnnotatedElement接口的实现类都重写了接口中的方法,都可以使用这些方法
实现类:AccessibleObject,Class,Constructor,Field,Method,Package
AnnotatedElement接口中的常用方法:
boolean isAnnotationPresent(Class<?> annotationClass) // 判断指定的对象(Class,Method...)上,是否包含指定的注解
/* 参数:
Class<?> annotationClass:判断哪个注解,就传递哪个注解的class文件对象
判断类上,方法上有没有Book注解,就需要传递Book.class
返回值:boolean
有指定的注解,返回true
没有指定的注解,返回false
*/
T getAnnotation(Class<T> annotationClass) // 获取对象(Class,Method...)上指定的注解
/* 参数:
Class<T> annotationClass:获取哪个注解,就传递哪个注解的class文件对象
获取类上,方法上的Book注解,就需要传递Book.class
返回值:
T:返回获取到的注解,获取的注解不存在,返回null
*/
Annotation[] getAnnotations() // 返回此元素上存在的所有公共注释。
Annotation[] getDeclaredAnnotations() // 返回直接存在于此元素上的所有注释,包含其他修饰符的注解
示例
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
@Book(value = "西游记",authors = {"吴承恩"})
public class Demo01ParseAnnotation {
@Book(value = "水浒传",authors = {"施耐庵","林冲"},price = 88.8)
public void method(){}
/* 解析类上的注解:获取类上注解的属性值 */
@Test
public void show01() throws ClassNotFoundException {
//1.获取当前类Demo01ParseAnnotation的class文件对象
Class clazz = Class.forName("com.itheima.demo09Annotation.Demo01ParseAnnotation");
//2.使用class文件对象中的方法isAnnotationPresent判断类上是否包含指定的Book注解
boolean b = clazz.isAnnotationPresent(Book.class);
System.out.println(b);//true
if(b){
//3.如果类上包含Book注解,使用class文件对象中的方法getAnnotation获取Book注解
Book book = (Book)clazz.getAnnotation(Book.class);
//4.使用注解名.属性名(),获取属性值
String value = book.value();
System.out.println(value);
double price = book.price();
System.out.println(price);
String[] authors = book.authors();
System.out.println(Arrays.toString(authors));
}
}
/* 解析方法上的注解:获取方法上注解的属性值 */
@Test
public void show02(){
//1.获取当前类Demo01ParseAnnotation的class文件对象
Class clazz = Demo01ParseAnnotation.class;
//2.使用class文件对象中的方法getMethods获取类中所有的方法,返回一个Method数组
Method[] methods = clazz.getMethods();
//3.遍历Method数组,获取每一个Method对象
for (Method method : methods) {
//4.使用Method对象中的方法isAnnotationPresent判断方法上是否包含指定的Book注解
boolean b = method.isAnnotationPresent(Book.class);
System.out.println(method.getName()+"-->"+b);
if(b){
//5.如果方法上有Book注解,使用Method对象中的方法getAnnotation,获取Book注解
Book book = method.getAnnotation(Book.class);
//6.使用注解名.属性名(),获取属性的值
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.authors()));
}
}
}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
//书名
public abstract String value();
//价格,默认值为 100
public abstract double price() default 100;
//多位作者
public abstract String[] authors();
}
注解和反射的综合案例
import java.lang.reflect.Method;
/*
注解和反射的综合案例
需求:
模拟Junit单元测试的@Test注解:作用可以单独的运行某一个方法
方法上添加了@Test注解,可以运行
方法上没有添加@Test注解,不可以运行
*/
public class Demo01Test {
public static void main(String[] args) throws Exception {
//3.获取测试类的class文件对象
Class clazz = Class.forName("com.itheima.demo10test.DemoMyTest");
//4.使用class文件对象中的方法newInstance实例化对象
Object obj = clazz.newInstance();
//5.使用class文件对象中的方法getMethods,获取类中所有的成员方法,返回一个Method数组
Method[] methods = clazz.getDeclaredMethods();//只会获取本类的方法,包含任意修饰符的,没有父类的方法
//6.遍历数组,获取每一个Method对象
for (Method method : methods) {
//7.使用Method对象中的方法isAnnotationPresent判断Method对象上是否有MyTest注解
boolean b = method.isAnnotationPresent(MyTest.class);
//8.如果Method对象上有MyTest注解,使用invoke运行方法
if(b){
method.invoke(obj);
}
}
}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//1.定义一个注解叫MyTest,使用元注解修饰(a.只能在方法上使用,b.运行时有效)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
//2.定义一个测试类,在测试类中定义多个方法,让部分方法使用MyTest注解修饰
public class DemoMyTest {
@MyTest
public void show01(){
System.out.println("show01方法");
}
public void show02(){
System.out.println("show02方法");
}
public void show03(){
System.out.println("show03方法");
}
@MyTest
public void show04(){
System.out.println("show04方法");
}
}