什么是注解?
- 注解(Annotation)也称为元数据,是一种代码级别的说明
- 注解是JDK1.5版本引入的一个特性,和类、接口是在同一个层次
- 注解可以声明在包、类、构造器、字段、方法、成员变量、局部变量、方法参数等的前面,用来对这些元素进行说明
注解:就是具有特殊含义的标记(注解是给机器阅读的)
概述
作用:对Java中类、方法、成员变量做标记,然后进行特殊处理,至于到底做何种处理由业务需求来决定。
注解就是在代码里添加一些特殊标志,这些标志可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或者进行操作。
注解与注释不同,会影响程序的执行,而注释不会。
注解的作用
- 编译检查
- @Override:用来修饰方法声明。
- 用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。
- 代码分析
- 通过代码里标识的注解对代码进行分析
- 框架的配置( 框架 = 代码 + 配置 )
- 具体使用请关注框架课程的内容的学习(注解去配置数据)
- 生成帮助文档
- @author:用来标识作者姓名
- @version:用于标识对象的版本号,适用范围:文件、类、方法
- 使用@author和@version注解就是告诉Javadoc工具在生成帮助文档时把作者姓名和版本号也标记在文档中
使用过的注解:
- 用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败
- Junit测试注解
- @FunctionalInterface //函数式接口
自定义注解
注解的定义格式
定义注解使用关键字:@interface
public @interface 注解名{ //内容 }
注解本质上就是一个接口。所有注解都会继承一个接口:Annotation
public interface 自定义注解名 extends java.lang.annotation.Annotation {}
带有属性的注解
- 属性的格式
- 格式1:数据类型 属性名(); //无默认值的属性
- 格式2:数据类型 属性名() default 默认值; //有默认值的属性
- 属性定义示例
public @interface Student { String name(); // 姓名 int age() default 18; // 年龄 String gender() default "男"; // 性别 } // 该注解就有了三个属性:name,age,gender
- 属性适用的数据类型【记住】
- 八种基本数据类型(int,short,long,double,byte,char,boolean,float)
- String,Class,注解类型,枚举类
- 以上类型的一维数组形式
注解的使用
注解的使用格式
//无属性注解 @注解名 例:@Test //有属性注解 @注解名(属性=值,属性=值)
注解可以在类上,成员变量上,构造方法上,方法上,参数上…
有默认值的属性,可以不用进行赋值。
案例:
public @interface Book { String name(); double price() default 100.0; String[] author(); }
建立一个BookStore的类,在类中定义成员变量,构造方法,成员方法
@Book(name = "西游记", author = {"吴承恩", "张三"}) public class BookStore { @Book(name = "三国", price = 10, author = {"罗贯中"}) private String book; @Book(name = "三国", price = 10, author = {"罗贯中"}) public BookStore() { } @Book(name = "三国", price = 10, author = {"罗贯中"}) public void test() { } }
如果注解中只有一个属性要赋值,而且名字是value,可以将value给省略,可以直接给值。
元注解
元注解的作用:
默认情况下,注解可以用在任何地方,比如类,成员方法,构造方法,成员变量等地方
如果要限制自定义注解的使用位置怎么办?那就要学习一个新的知识点:元注解
结论:元注解是用来约束自定义注解的使用范围、生命周期。
常用元注解:
常用元注解:@Target、@Retention
@Target
- 作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用
- @Target可选的参数值在枚举类ElemenetType中包括:
TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上
PACKAGE:包
ANNOTATION_TYPE:注解上
@Retention
- 作用:定义该注解的生命周期(有效范围)。
- @Retention可选的参数值在枚举类型RetentionPolicy中包括:
SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了
使用场景:针对一些检查性的操作,比如:@Override ,就使用SOURCE注解
CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有(默认值 )
使用场景:在编译时进行一些预处理操作,比如:生成一些辅助代码,就用CLASS注解
RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解
使用场景:要在运行时去动态获取注解信息,那只能用 RUNTIME 注解
元注解的使用:
@Target({ElementType.METHOD,ElementType.TYPE}) //元注解 @interface Stu{ String name(); } //类 @Stu(name="jack") //成功 public class AnnotationDemo02 { // 成员变量 @Stu(name = "lily") // 编译失败 private String gender; // 成员方法 @Stu(name="rose") //成功 public void test(){ } // 构造方法 @Stu(name="lucy") // 编译失败 public AnnotationDemo02(){} }
注解解析
获取注解数据的原理:
想要对注解中的数据进行解析,需要借助:AnnotatedElement
接口
- Field,Method,Constructor,Class 等类都是实现了
AnnotatedElement
接口
AnnotatedElement
是一个接口,定义了解析注解的方法:
1. boolean isAnnotationPresent(Class<Annotation> annotationClass) 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false public class BookStore{ @Book(name="书名") public void buy() // Method对象 判断该对象上是否使用了@Book注解 { // boolean flag = Method对象.isAnnotationPresent(Book.class) } } 2. T getAnnotation(Class<T> annotationClass) 根据注解类型获得对应注解对象 // 获取注解的具体值见下面的综合案例 // 获取对象上的自定义注解 // Book bookAnno = Method对象.getAnnotation(Book.class); // bookAnno.属性 //获取注解中属性的值
Class,Constructor,Method,Field都会实现AnnotatedElement 接口
- 解析类型上的注解:借助字节码对象(Class对象)
- 解析构造方法上的注解:借助构造器对象(Constructor对象)
- 解析方法上的注解:借助方法对象(Method对象)
- 解析字段上的注解:借助字段对象(Field对象)
注解解析的步骤:(注解是书写在:类、方法、变量上)
1、利用反射技术获取注解作用的对象:类、方法、变量、构造方法
2、判断对象上是否有自定义注解存在
3、有:获取对象上的自定义注解
4、使用获取到的自定义注解对象,拿到注解中的属性值
注意:注解解析必须保证自定义注解生命周期在RUNTIME(程序运行中)
案例:
需求如下:
- 定义注解Book,要求如下:
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 限制注解使用的位置:类和成员方法上 【Target】
- 指定注解的有效范围:RUNTIME 【Retention】
- 定义BookStore类,在类和成员方法上使用Book注解
- 定义TestAnnotation测试类获取Book注解上的数据
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD,ElementType.TYPE}) //使用范围:方法、类 @Retention(RetentionPolicy.RUNTIME) //保证注解在程序执行时有效(适用于注解解析) public @interface Book { String value(); double price() default 100; String[] authors(); } //类 public class BookStore { @Book(value = "Java入门", authors = {"张老师", "毕老师"}) public void buy() { System.out.println("购书....."); } } //对注解中的数据进行解析 public class TestBookStore { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //反射套路:1、Class 2、构造器 3、Method Class<BookStore> bookStoreClass = BookStore.class; //获取构造器 Constructor<BookStore> con = bookStoreClass.getConstructor(); BookStore bookStore = con.newInstance();//实例化对象 //获取Method Method method = bookStoreClass.getMethod("buy"); //解析注解的步骤 if(method.isAnnotationPresent(Book.class)){ //获取Method对象上的Book注解 Book bookAnno = method.getAnnotation(Book.class); //获取注解上的数据 String bookName = bookAnno.value(); double bookPrice = bookAnno.price(); String[] bookAuthors = bookAnno.authors(); System.out.println("书名:"+bookName); System.out.println("价格:"+bookPrice); System.out.println("作者:"+ Arrays.toString(bookAuthors)); } } }
小结
注解解析的步骤:
- 利用反射技术获取相关的对象:类、构造器、方法、变量
- 使用方法getAnnotation,获取自定义注解对象
- 使用注解对象,分别获取注解中的属性值
注解 综合案例
案例分析
- 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
- 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
- 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行
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 MyTest { //无属性注解 } public class TestAnnotationParse { //方法1 @MyTest public void method1(){ System.out.println("我是方法1"); } @MyTest public void method3(){ System.out.println("我是方法3"); } public void method2(){ System.out.println("我是方法2"); } } public class Test { public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException { //获取Class对象 Class<TestAnnotationParse> testAnnotationParseClass = TestAnnotationParse.class; //获取Class对象中,所有的Method方法 Method[] methods = testAnnotationParseClass.getMethods(); //遍历数组 for (int i = 0; i < methods.length; i++) { //获取每一个Method对象 Method method = methods[i]; //判断Method对象上,是否存在@MyTest if(method.isAnnotationPresent(MyTest.class)){ method.invoke(testAnnotationParseClass.newInstance()); } } } }
小结
注解解析的步骤:
- 获取Class对象 //类名.class、对象名.class、Class.forName(“…”)
- 基于Class对象,来获取:构造器/成员方法/成员变量 //getConstructor() getMethod() getField()
- 基于构造器/成员方法/成员变量,判断是否存在自定义注解 // isAnnotationPresent(注解.class)
- 存在:获取自定义注解对象 // 注解 对象 = 构造器/方法/变量/类 .getAnnotation(注解.class)
- 基于自定注解对象,获取其属性值 // 注解对象.属性()
注解+反射 综合案例
@Biao(value = "类上的value", name = "类上的name") public class TestBiao { private Integer id; private String name; void test(){ System.out.println("test"); } String eat(Integer id){ return "eat..." + id + new Date().getTime(); } @Biao(value = "方法上的value", name = "方法上的name") void say(){ System.out.println("say"); } public static void main(String[] args) throws Exception{ Class<?> clazz = Class.forName("testjava.annotation.TestBiao"); if (clazz.isAnnotationPresent(Biao.class)) { Biao annotation = clazz.getAnnotation(Biao.class); System.out.println(annotation.name()); System.out.println(annotation.value()); } Method[] methods = clazz.getDeclaredMethods(); if(methods == null){ return; } for (Method method : methods) { boolean annotationPresent = method.isAnnotationPresent(Biao.class); if(annotationPresent){ Biao annotation = method.getAnnotation(Biao.class); System.out.println(annotation.name()); System.out.println(annotation.value()); System.out.println("执行方法"); method.invoke(clazz.newInstance()); } } } }
打印如下:
/**
- 类上的name
- 类上的value
- 方法上的name
- 方法上的value
- 执行方法
- say
*/