本周任务中触及到自定义注解的使用, 于是就专门学习了一下注解.
学习注解有什么好处?
spring, mybatis等框架中, 大量使用了注解, 想要更易于理解框架, 需要我们对注解有一定的了解.
什么是注解?
注解是JDK5
开始引入的概念, 他可以将任何的信息或者元数据(元数据即: 描述数据的数据)与程序的元素(类, 方法, 成员变量等)进行关联. 为这些程序的元素加上直观明了的说明. 这些与业务逻辑是无关的, 大家可以理解为注解就是为这些元素(类, 方法, 成员变量等)贴一个标签.
注解可以用在包, 类, 构造方法, 方法, 成员变量, 参数以及,本地变量的声明语句中.
java中常见的标准注解
注解我们无处不见, 大多为自定义注解, 存在于框架中, 实际上我们也常常见到在JDK
中的几个自带的注解:@Override
, @Deprecated
, @SuppressWarnings
这三个注解大家一定也不陌生.
-
@Override
表示覆盖或者重写父类的方法
-
@Deprecated
表示该方法已过时.
-
@SuppressWarnings
表示忽略指定警告.
使用方式演示:
public class JdkAnnotation {
// 使用该注解可以忽略unused警告
@SuppressWarnings("unused")
public static void main(String[] args) {
// 如果没有使用该变量, 编译器会有警告: The value of the variable string is not used
String string = "unused";
}
// 该注解标志该方法已过时
@Deprecated
public void myDeprecatedMethod(){
// jdk9中的finalized()已被标注为过时方法
}
// 重写父类方法
@Override
public String toString() {
return super.toString();
}
}
以上就是JDK
自带的标准注解.其实除了这三个意外, 还有JDK7
中加入的@SafeVarargs
和JDK8
中加入的@FunctionalInterface
, 大家感兴趣的话可以查阅一下相关资料深入了解.
自定义注解
我们经常使用的例如spring中的注解, 都是spring框架中自定义的注解. 我们需要了解它的话需要了解什么是自定注解, 如何自定义? 我们先看看自定义注解是什么样子的, 再进一步说明.
元注解
看了图片或许大家不明白其中的元注解一词, 我们先了解一下元注解是什么.
看到带一个元字, 不难想到和元数据类似, 元数据是描述数据的数据, 而元注解当然就是描述注解的注解.
这样解释可能不太好理解, 简单来说可以说是, 再自定义注解的时候, 我们需要把这个注解的生命周期, 作用域给设置, 这些就是使用元注解完成的.
大概了解了一下元注解的意思, 那么我在上图例子中, 使用的三个元注解分别代表什么意思, 接下来逐一介绍一下.
-
@Target
是用于描述该自定义注解的作用域: 表示该注解用于哪部分, 比如我上图中的例子使用ElementType.TYPE
标注该自定义注解用于类上.如果作用域类型有多个用英文逗号分隔
其具体参数列表如下:
- ElementType.CONSTRUCTOR: 用于描述构造器
- ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
- ElementType.LOCAL_VARIABLE: 用于描述局部变量
- ElementType.METHOD: 用于描述方法
- ElementType.PACKAGE: 用于描述包
- ElementType.PARAMETER: 用于描述参数
- ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明
-
@Retention
是用于定义该注解的生命周期, 比如我上图中使用了RetentionPolicy.RUNTIME
是指运行期也保留的注解. 这样可以通过反射获取到该自定义注解信息.
其具体参数列表如下:
- RetentionPolicy.SOURCE : 在编译阶段丢弃. 这些注解在编译结束之后就不再有任何意义, 所以它们不会写入字节码. @Override, @SuppressWarnings, @Deprecated都属于这类注解.
- RetentionPolicy.CLASS : 在类加载的时候丢弃. 在字节码文件的处理中有用. 注解默认使用这种方式
- RetentionPolicy.RUNTIME : 始终不会丢弃, 运行期也保留该注解, 因此可以使用反射机制读取该注解的信息. 我们自定义的注解通常使用这种方式.
-
@Documented
这是一个标记该注解信息会添加到java文档中
除了上述我例子中使用到三个元注解以外,还有两个元注解:
-
@Inherited
是一个标记注解,@Inherited
阐述了某个被标注的类型是被继承的. 如果一个使用了@Inherited
修饰的annotation 类型被用于一个class, 则这个annotation 将被用于该class 的子类.
如果读不懂这个概念, 这里我特地说明一下: 这里的继承意思不是使用这个元注解的自定义注解的本身可以被继承, 而是如果一个超类被@Inherited
注解过的自定义注解进行注解的话, 那么它的字类如果没有被任何注解应用的话, 那么这个子类就会继承这个超类的注解.
或许还是说得比较抽象, 我们看看代码:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
自定义注解Test
, 被@Inherited
修饰, 之后类A
被Test
注解, 类B
继承了类A
也拥有Test
这个注解.
用互联网中流传的一个形象例子再描述一下一定可以明白:
老子非常有钱,所以人们给他贴了一张标签叫做富豪。
老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。
老子的孙子长大了,自然也是富豪。
这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。
-
@Repeatable
当然就是可重复的意思, 这个是JDK1.8
中引进的新元注解.
什么样的注解会被多次重复应用呢? 不难想到又时候需要值取多个的时候.
比如一个人担任多个角色, 比如我的总监不仅是一个程序员, 还得兼任UI.
我们还是看看代码:
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="coder")
@Person(role="UI")
public class manager{
}
大家可以看到上面代码中, @Repeatable
注解了Person
. 而 @Repeatable
后面括号中的类相当于一个容器注解.
什么是容器注解呢?就是用来存放其它注解的地方. 它本身也是一个注解.
我们再看看代码中的相关容器注解:
@interface Persons {
Person[] value();
}
按照规定, 它里面必须要有一个 value 的属性, 属性类型是一个被 @Repeatable 注解过的注解数组, 注意它是数组.
注解的属性
我们总能在例子中看到型如: String value()
, Person[] value()
这样的代码段, 这些就是注解的属性, 也叫做成员变量. 看到括号, 或许大家第一时间想到了方法, 但注解只有成员变量, 没有方法. 我们还是看看使用方式.
定义注解的代码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TaskDesc {
String value();
String status() default "N";
}
使用注解的代码:
@TaskDesc(value="第一个定时任务", status="Y")
public class TaskOne {
}
而由于status
是有默认值的,所以我们可以不必对其进行值的定义, 使用默认的值也可以, 这样的话我们只需要定义value
就可以了. 而在注解中有这么一个特殊规定, 如果只需要使用value
的话, 应用这个注解时可以直接接属性值填写到括号内, 比如这样:
@TaskDesc("第一个定时任务")
public class TaskOne {
}
这样的话, 如果进行取值的话, 通过value
可以得到"第一个定时任务"
,通过status
可以得到默认值"N"
.
该特性体现在多处, 我们可以看到例如springMVC
的requestMapping
在使用method
为GET
请求时候, 可以只这样写requestMapping("/xxx")
, 但是如果需要修改请求方式为POST
则需要写成:requestMapping(value="/xxx", method = RequestMethod.POST)
注解的提取
我们已经讨论了自定义注解的生命与使用了, 那么我们也知道仅仅将这些注解使用到我们想贴的位置, 对我们任何帮助都没有, 它仅仅是标签而已, 我们需要对拥有该标签的内容进行识别, 提取, 使用.
那么如何提取呢?
我们可以通过反射来进行这一系列的操作.
注解与反射.
注解可以通过反射来获取.
- 我们可以先通过
isAnnotationPresent()
方法判断它是否应用了某个注解.
- 然后通过
getAnnotation()
来获取Annotation
对象
- 获得到
Annotation
对象后, 我们可以调用它的属性来获取值.
这里我们看看代码:
自定义注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
String value();
String msg() default "i am msg";
}
应用该注解的类:
@TestAnnotation("2333")
public class Test {
}
提取注解信息:
public class Application {
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if (hasAnnotation) {
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("value:" + testAnnotation.value());
System.out.println("msg:" + testAnnotation.msg());
}
}
}
控制台打印结果
value:2333
msg:i am msg
注解到底用在哪?
其实注解的定义已经告诉过我们了, 它不会影响代码, 只是某些内容的标签, 我们可以利用这些标签来区别一些内容, 来做一些处理, 比如我刚刚的例子中, 如果提取到的value
值等于2333
时, 我们做某种操作, 而值为其他时, 我们又做其他操作. 这样的场景.
还是看不明白的话, 还是可以说说上文提到的springMVC
中requestMapping
, 当我们传的method
是GET
时候, 是对应解析GET
请求, 而如果是POST
时, 解析的是POST
请求. 这样的例子.
其实我们能在框架看到注解的地方, 就是它使用场景了.
结束语
今天对注解的讨论就到这儿了, 希望大家有所收获, 如果文中有所纰漏, 请大家指正, 感谢阅读.
………………………………