【Java基础】注解详述&模拟Junit框架
一.概述
Java注解(Annotation)又称Java标注。我们可以使用注解来标注类,构造器,方法,成员变量,参数等。
问题来了,我们标注起来有什么作用呢?
程序在编译或者运行时可以检测到这些标记,我们或者框架底层可以对有标注的类,方法等进行一些特殊的处理,例如:
- 标记了注解
@Test
的方法就可以被当作测试方法执行,而没有标记的就不能当成测试方法执行。 - 我们在使用框架进行开发的过程中常常会用到注解来简化开发过程,例如
@Resource
,@Component
等。 - 将
@Override
放在方法前,在编译时会进行格式检查,如果你这个方法并不是覆盖了超类方法则会报错。
注解就如同公路边的一个个路标,在你碰到的时提醒你注意一些事或者教你该做一些事了,就好比你看到一个急转弯路标,你就知道,该减速行驶了~
二.自定义注解
除了使用Java给我们提供的那些注解,我们还可以自己定义一个注解来使用。
语法格式:
public @interface 注解名称 {
public 属性类型 属性名称() default 默认值;
}
注意事项:
注解类里可以声明零个至多个属性
声明与接口类
interface
有点相似,不过前面多了@
符号!!!属性类型为
public
时,public
可以省略不写属性名称后面必须跟上
()
default 默认值
可以不写,不写时使用注解必须提供该属性值,有默认值则可以不提供。如果注解类中没有属性或者属性全有默认值,在使用时可以直接写成
@注解名称
以上只是简单形式,我们下面还会使用元注解进行完善。
使用示例:
// 例一
public @interface Book {
public String bookName() default "";
double price();
}
// 例二
public @interface Money {
}
// 可以用在类,方法,字段等地方
@Book(bookName = "《活着》", price = 29.9)
public class MyClient {
// 可以都写上属性值
@Book(bookName = "《活着》", price = 29.9)
private String name;
// bookName 有默认值也可以不写
// price 无默认值必须赋值
@Book(price = 29.9)
public void save() {
System.out.println("test");
}
// 可以不写()
@Money
public void save() {
System.out.println("test");
}
}
特殊属性:
- 如果注解中只有一个名为
value
的属性,使用注解的时候可以省略value的名称。 - 如果有多个属性且多个属性都有默认值,也可以省略value的名称
// 两种形式都可以省略
public @interface Book {
String value();
}
public @interface Book {
public String bookName() default "";
String value();
}
public class MyClient {
// 使用时可以选择直接写上值
// @Book(value = "test")
@Book("test")
public void save() {
System.out.println("test");
}
}
三.元注解
(1) 介绍
用来注解注解的注解。意思就是我们可以在注解类上加上一些Java给我们提供的特定注解,用于对其的使用与状态进一步的标注。
常见的元注解有两个:
@Target
:用于约束自定义注解只能在哪些地方使用。例如我们上述自定义注解便没有进行约束以至于可在任意允许的地方使用。@Retention
:申明注解的声明周期,例如标注它只存在于编译阶段或者运行阶段或者一直存在。
常用值:
@Target
中可使用的值定义在ElementType枚举类中,常用值如下TYPE
:类,接口FIELD
:成员变量METHOD
:成员方法PARAMETER,方法参数CONSTRUCTOR,构造器LOCAL_VARIABLE
:局部变量
@Retention
中可使用的值定义在RetentionPolicy枚举类中,常用值如下SOURCE
:注解只作用在源码阶段,生成的字节码文件中不存在CLASS
:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值.RUNTIME
:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)
(2) 使用
我们再使用元注解对上述自定义注解进行完善:
// 标注该自定义注解存活于运行阶段
@Retention(RetentionPolicy.RUNTIME)
// 标注该自定义注解只能使用在方法上
@Target(ElementType.METHOD)
public @interface Book {
public String bookName() default "";
double price();
}
// 测试类
// @Book(bookName = "666",price = 666) 报错
public class MyClient {
// @Book(bookName = "666",price = 666) 报错
private String name;
@Book(bookName = "666",price = 666)
public void save() {
System.out.println("test");
}
}
再次进行测试我们发现该注解只能在方法上使用,在其他地方使用便会产生错误。
当然我们也可以给注解声明多个作用范围:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD}) // 如此便可同时用于字段和方法上
public @interface Book {
public String bookName() default "";
double price();
}
// 测试类
// @Book(bookName = "666",price = 666) 报错
public class MyClient {
@Book(bookName = "666",price = 666)
private String name;
@Book(bookName = "666",price = 666)
public void save() {
System.out.println("test");
}
}
四.注解的解析
(1) 介绍
我们可以通过反射判断方法或者类等是否存在注解,存在注解我们可以解析出内容。
解析常用方法:
方法 | 说明 |
---|---|
Annotation[] getDeclaredAnnotations() | 获得当前对象上使用的所有注解,返回注解数组。 |
T getDeclaredAnnotation(Class<T> annotationClass) | 根据注解类型获得对应注解对象 |
boolean isAnnotationPresent(Class<Annotation> annotationClass) | 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false |
解析注解的技巧:
注解在哪个成分上,我们就先拿哪个成分对象。
比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解
比如注解作用在类上,则要该类的Class对象,再来拿上面的注解
比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解
(2) 使用
同样是上述案例,我们来尝试解析获取一些注解内的内容。
// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Book {
public String bookName() default "";
double price();
}
// 测试方法
@Book(bookName = "类注解",price = 999)
public class MyClient {
@Book(bookName = "方法注解",price = 666)
public void save() {
System.out.println("test");
}
}
我们尝试利用反射来解析一下
public class Test{
public static void main(String[] args) throws NoSuchMethodException {
// 1. 先得到类对象
Class<MyClient> c = MyClient.class;
// 2. 判断这个类上是否存在这个注解
if (c.isAnnotationPresent(Book.class)){
// 3. 直接获取该注解对象
Book book = c.getDeclaredAnnotation(Book.class);
// 4. 获取属性值
System.out.println("=====类注解======");
System.out.println(book.bookName());
System.out.println(book.price());
}
// 1. 先得到方法对象
Method method = c.getDeclaredMethod("save");
// 2. 判断这个方法上是否存在这个注解
if (method.isAnnotationPresent(Book.class)){
// 3. 直接获取该注解对象
Book book = method.getDeclaredAnnotation(Book.class);
// 4. 获取属性值
System.out.println("=====方法注解======");
System.out.println(book.bookName());
System.out.println(book.price());
}
}
}
运行可以看到我们成功获取到了属性值
五.模拟Junit框架
注解与反射在我们的框架的底层实现中有着广泛的应用。接下来我们便来模拟一下Junit框架。
- 我们的目的是:只要是加了自定义注解MyTest的方法,就可以在启动时被触发执行
- 定义一个自定义注解MyTest,要求只能注解方法并且一直都存在
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyTest {
}
- 定义若干个方法,只要有@MyTest注解的方法就能在启动时被触发执行,没有这个注解的方法不能执行
public class MyClient {
@MyTest
public void test1() {
System.out.println("test1......");
}
@MyTest
public void test2() {
System.out.println("test2......");
}
public void test3() {
System.out.println("test3......");
}
@MyTest
public void test4() {
System.out.println("test4......");
}
}
- 由于Junit框架是与IDEA进行了整合所有左边才有可直接run的绿三角,我们需要自己通过反射触发。
// 在MyClient里添加一个main方法,通过反射模拟启动菜单
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
// 用于触发方法
MyClient myClient = new MyClient();
// 1. 先得到类对象和得到方法对象
Class<MyClient> c = MyClient.class;
Method[] methods = c.getDeclaredMethods();
// 2. 遍历判断方法上是否存在这个注解
for (Method method : methods) {
if (method.isAnnotationPresent(MyTest.class)){
// 3. 触发方法
method.invoke(myClient);
}
}
}
- 启动可以看到只有我们添加了注解的1,2,4执行了,而没添加的则未执行
如此我们便简单实现了Junit框架
- 点赞
- 收藏
- 关注作者
评论(0)