1.注解的概念以及自定义注解
注解就是Java
中的特殊标记,比如 Junit
中的 @Test
注解以及标记方法重写的 @Override
等。注解的作用是让其他程序根据注解信息来决定怎么执行该程序。
想揭开注解的面纱我们得先从它的语法入手,下面是定义注解的语法。
java
public @interface 名称{
public 属性类型 属性名() default 默认值;
}
自定义一个注解:
java
public @interface MyAnnotation {
//属性1
String s();
//属性2 默认值 true
boolean b() default true;
//属性3 类型为字符串数组
String[] arr();
}
使用规则:
java
//可以在类上面使用
@MyAnnotation(b="hello",arr={"value1","value2","value3"})
public class Test {
//可以在属性上使用
@MyAnnotation(s="hello",b = false,arr={"value1","value2","value3"})
public String str;
//可以在方法上面使用
@MyAnnotation(b="hello",arr={"value1","value2","value3"})
public void test1(){
}
}
-
注解里面有多少个非
default
属性,使用的时候就得初始化几个属性。 -
如果注解中只有一个
value
属性(default
不算),使用注解时,value
名称可以不写。比如:javapublic @interface MyAnnotation { //属性1 String value(); //属性2 默认值 true boolean b() default true; }
java@MyAnnotation("sdfd") //前提是属性名称必须为 value public class Test { }
2.元注解
元注解就是修饰注解的注解。
@Target(ElementType.TYPE)
:声明被修饰的注解只能在哪些位置使用。- TYPE:类,接口
- FIELD:成员变量
- METHOD:成员方法
- PARAMETER:方法参数
- CONSTRUCTOR:构造器
- LOCAL_VARIABLE:局部变量
@Retention(RetentionPolicy.RUNTIME)
:- SOURCE:只作用在源码阶段,字节码文件中不存在
- CLASS:保留到字节码文件阶段,运行阶段不存在
- RUNTIME:一直保留到运行阶段
java
@Target({ElementType.TYPE,ElementType.METHOD})//当前 MyAnnotation 注解只能用在类和方法上
@Retention(RetentionPolicy.RUNTIME)//一直保留到运行阶段
public @interface MyAnnotation {
String a();
boolean b() default true;
String[] arr();
}
在@Target
注解中,属性是 ElementType[]
类型,所以可以有多个值。
3.注解的解析与作用【模拟 Junit 框架中 @Test 注解】
注解到底有什么用呢?上面我们自定义了一个注解,貌似啥用都没有。其实不然,注解一般要与反射结合使用,在才能发挥它的最大作用。接下来就来简单地模拟 Junit
中的 @Test
注解,看看注解与反射如何结合使用。下面先给出反射中与注解相关的 API。
在反射中,Class、Method、Field、Constructor
,都实现了 AnnotatedElement
接口。这个接口提供一些方法:
方法名 | 作用 | 返回值 |
---|---|---|
isAnnotationPresent(Class<? extends Annotation> annotationClass) |
判断指定类型的注解是否出现在当前元素上 | boolean |
getAnnotation(Class<T> annotationClass) |
获取当前元素上指定类型的注解,如果没有则返回 null | T |
getAnnotations() |
获取当前元素上的所有注解,如果没有则返回长度为 0 的数组 | Annotation[] |
getAnnotationsByType(Class<T> annotationClass) |
获取当前元素上与指定类型相关联的注解,包括直接存在、间接存在和继承的注解,如果没有则返回长度为 0 的数组 | T[] |
getDeclaredAnnotation(Class<T> annotationClass) |
获取当前元素上直接存在的指定类型的注解,如果没有则返回 null | T |
getDeclaredAnnotations() |
获取当前元素上直接存在的所有注解,如果没有则返回长度为 0 的数组 | Annotation[] |
getDeclaredAnnotationsByType(Class<T> annotationClass) |
获取当前元素上直接或间接存在的指定类型的注解,如果没有则返回长度为 0 的数组 | T[] |
Junit
中的 @Test
注解有什么用呢?@Test
用来标识一个方法是一个测试方法的标记。要运行这个方法,Junit
首先会创建一个类的新实例,然后调用这个注解的方法。如果这个方法抛出了异常,Junit
会将其报告为失败。如果没有抛出异常,测试就被认为成功了。
那么是怎么做到直接加上 @Test 就能直接运行该方法呢?
这里我们先自定义一个注解:
java
@Target(ElementType.METHOD) //只能注解方法
@Retention(RetentionPolicy.RUNTIME) //一直到运行阶段
public @interface MyTest {
}
加上我们自己的注解:
java
public class Test {
@MyTest
public void test1(){
System.out.println("启动 test1 成功!");
}
public void test2(){
System.out.println("启动 test2 成功!");
}
@MyTest
public void test3(){
System.out.println("启动 test3 成功!");
}
}
重点来了,我们这里需要实现一个 Main 方法
java
public class Main {
public static void main(String[] args) throws Exception {
Test test = new Test();
//1.获得 Test 类的 class对象
Class<?> c = Test.class;
//2.获取该对象的所有方法
Method[] methods = c.getDeclaredMethods();
//3.遍历数组中的每个方法,提取有 @MyTest 注解的方法并运行该方法
for(Method m : methods){
//判断当前方法是否被 @MyTest 注解修饰
if(m.isAnnotationPresent(MyTest.class)){
//运行该方法
m.invoke(test);
}
}
}
}
点击运行,就能让所有的加了 @MyTest
注解的方法运行:
再来总结一下:注解是一种为类、方法、字段、参数等元素添加元数据的机制,它可以用来描述、限制、扩展或修改这些元素的行为。注解本身并不会影响程序的逻辑,它只是提供了一些额外的信息,需要由其他程序(比如编译器、框架、工具等)来读取和处理。
注解和反射的关系是:注解可以被反射读取和处理,反射可以利用注解来实现一些高级的功能。注解和反射的组合使用是一种常见的编程模式,它可以简化代码、增强灵活性、提高效率。例如,Java中的内置注解@Deprecated
、@Override
、@SuppressWarnings
等都是通过反射来实现编译时或运行时的检查和提示的。又例如,Spring
框架中的@Autowired
、@Controller
、@RequestMapping
等注解都是通过反射来实现依赖注入、控制器映射、请求处理等功能的。
4.注解的本质
注解到底是怎么实现的呢?这里通过把MyAnnotation
的.class
文件的反编译成.java
看看是什么样的(利用Xjad
反编译工具)。
注解本质是一个继承了Annotation
的特殊接口,其具体实现类是Java
运行时生成的动态代理类。注解格式"@注解名(...)"
,本质上就是该注解的一个实现类对象,实现了该注解以及Annotation
接口。具体的底层原理可以看这篇文章👉Java注解(Annotation)原理详解_annotation原理-CSDN博客,写得真的不错(俺也是参考了这篇文章)。