java jdk: 10.0.2
注解的定义
注解通过 @interface
关键字进行定义.
public @interface TestAnnotation {
}
它的形式跟接口很类似,不过前面多了一个 @
符号. 上面的代码就创建了一个名字为 TestAnnotaion
的注解.
你可以简单理解为创建了一张名字为 TestAnnotation
的标签.
注解的应用
上面创建了一个注解, 那么注解的的使用方法是什么呢.
@TestAnnotation
public class Test {
}
创建一个类 Test
, 然后在类定义的地方加上 @TestAnnotation
就可以用 TestAnnotation
注解这个类了.
你可以简单理解为将 TestAnnotation
这张标签贴到 Test
这个类上面.
不过, 要想注解能够正常工作, 还需要介绍一下一个新的概念那就是元注解.
元注解
元注解是什么意思呢?
元注解是可以注解到注解上的注解, 或者说元注解是一种基本注解, 但是它能够应用到其它的注解上面.
如果难于理解的话, 你可以这样理解. 元注解也是一张标签, 但是它是一张特殊的标签, 它的作用和目的就是给其他普通的标签进行解释说明的.
元标签有 @Retention
、 @Documented
、@Target
、 @Inherited
、 @Repeatable
5 种.
@Retention
Retention
的英文意为保留期的意思. 当 @Retention
应用到一个注解上的时候, 它解释说明了这个注解的的存活时间.
它的取值如下:
-
RetentionPolicy.SOURCE
注解只在源码阶段保留, 在编译器进行编译时它将被丢弃忽视. -
RetentionPolicy.CLASS
注解只被保留到编译进行的时候, 它并不会被加载到 JVM 中. -
RetentionPolicy.RUNTIME
注解可以保留到程序运行的时候, 它会被加载进入到 JVM 中, 所以在程序运行时可以获取到它们.
我们可以这样的方式来加深理解, @Retention
去给一张标签解释的时候, 它指定了这张标签张贴的时间.
@Retention
相当于给一张标签上面盖了一张时间戳, 时间戳指明了标签张贴的时间周期.
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}
上面的代码中, 我们指定 TestAnnotation
可以在程序运行周期被获取到, 因此它的生命周期非常的长.
@Documented
顾名思义, 这个元注解肯定是和文档有关. 它的作用是能够将注解中的元素包含到 Javadoc 中去.
@Target
Target 是目标的意思, @Target
指定了注解运用的地方.
你可以这样理解, 当一个注解被 @Target
注解时, 这个注解就被限定了运用的场景.
-
ElementType.ANNOTATION_TYPE
可以给一个注解进行注解 -
ElementType.CONSTRUCTOR
可以给构造方法进行注解 -
ElementType.FIELD
可以给属性(字段)进行注解 -
ElementType.LOCAL_VARIABLE
可以给局部变量进行注解 -
ElementType.METHOD
可以给方法进行注解 -
ElementType.PACKAGE
可以给一个包进行注解 -
ElementType.PARAMETER
可以给一个方法内的参数进行注解 -
ElementType.TYPE
可以给一个类型进行注解, 比如类、接口、枚举
@Inherited
Inherited
是继承的意思, 但是它并不是说注解本身可以继承, 而是说如果一个超类被 @Inherited
注解过的注解进行注解的话, 那么如果它的子类没有被任何注解应用的话, 那么这个子类就继承了超类的注解.
说的比较抽象. 代码来解释.
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
注解 Test
被 @Inherited
修饰, 之后类 A 被 Test
注解, 类 B 继承 A, 类 B 也拥有 Test 这个注解.
@Repeatable
Repeatable
自然是可重复的意思.
什么样的注解会多次应用呢? 通常是注解的值可以同时取多个.
举个例子, 一个人他既是程序员又是产品经理, 同时他还是个画家.
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
注意上面的代码, @Repeatable
注解了 Person
. 而 @Repeatable
后面括号中的类相当于一个容器注解.
什么是容器注解呢? 就是用来存放其它注解的地方. 它本身也是一个注解.
我们再看看代码中的相关容器注解.
@interface Persons {
Person[] value();
}
按照规定, 它里面必须要有一个 value 的属性, 属性类型是一个被 @Repeatable
注解过的注解数组, 注意它是数组.
注解的属性
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
上面代码定义了 TestAnnotation
这个注解中拥有 id
和 msg
两个属性. 在使用的时候, 我们应该给它们进行赋值.
赋值的方式是在注解的括号内以 value=””
形式, 多个属性之前用 ,
隔开.
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
⚠️ 在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组.
判断类上是否使用指定注解
Map<String, Class<?>> stringClassMap = ClassUtil.getClasses("org.itzhizhe.testReflection");
stringClassMap.forEach((key, value) ->{
ReflectionClass annotation = value.getAnnotation(ReflectionClass.class);
if (annotation != null) {
System.out.println("class name: " + value.getSimpleName() + ", 注解 value : " + annotation.Value());
}
});
判断字段上是否使用指定注解
Map<String, Class<?>> stringClassMap = ClassUtil.getClasses("org.itzhizhe.testReflection");
stringClassMap.forEach((key, value) -> {
for (Field declaredField : ClassUtil.getAnnotatedDeclaredFields(value, ReflectionField.class, false)) {
ReflectionField annotation = declaredField.getAnnotation(ReflectionField.class);
if (annotation != null) {
this.put(annotation.Value(), declaredField);
System.out.println("class name: " + value.getSimpleName() +
", 字段名: " + declaredField.getName() +
", 字段类型:" + declaredField.getType().getSimpleName() +
", 注解值:" + annotation.Value()
);
}
}
});
判断方法上是否使用指定注解
Map<String, Class<?>> stringClassMap = ClassUtil.getClasses("org.itzhizhe.testReflection");
stringClassMap.forEach((key, value) -> {
for (Method declaredField : ClassUtil.getAnnotatedDeclaredMethods(value, ReflectionMethod.class,false)) {
ReflectionMethod annotation = declaredField.getAnnotation(ReflectionMethod.class);
if (annotation != null) {
System.out.println("class name: " + value.getSimpleName() +
", 方法名: " + declaredField.getName() +
", 方法返回类型:" + declaredField.getReturnType() +
", 注解值:" + annotation.id()
);
}
}
});
创建对象
value.getConstructor().newInstance();
上面代码是使用无参构造来创建对象, 当然也可以使用有参构造来创建对象.
getConstructor()
参数填写构造参数的参数类型.
newInstance()
参数填写传给构造参数的值.
调用方法
可以通过 Method
的 setAccessible
方法设置为 true
, 就可以调用对象的 private
方法.
declaredField.setAccessible(true);
declaredField.invoke(value);
然后可以使用 invoke
方法来进行方法调用. 第一个参数为要调用方法的对象. 剩下的参数是方法参数.
总结
如果想实现像 Spring 这种框架, 实现上面几步是必须.
比如依赖注入, 我们需要知道字段的类型, 然后到 IOC 容器中找到对应的对象进行赋值.
而方法调用你可以先将注解的方法进行保存, 比如保存到 Map 集合中.
例如客户端发送指定数据帧后, 然后通过 key 取出要执行的方法后执行就可以.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。