前面两节学习了springboot
的基本使用,其中大量使用了注解来减少代码量,想必大家都觉得挺奇怪的吧。
所以第三节,稍微停顿一下增删改查的脚步,补补一些基础(๑•̀ㅂ•́)و✧。
对于前端来说,注解这个概念很陌生,如此神秘的力量是如何发挥作用的呢,今天学习一下java
中的注解:一种形如@xxx
的东东,xxx一般是大写字母开头。
注解Annotation
是在java源码
中对于类、方法、字段、方法参数的一种特殊注释。
之所以说它是注释
,是因为注解
本身并不会对代码逻辑造成任何影响,对于如何使用注解去完成对应的功能是工具或者说某些容器的事,从这一点出发,感觉挺像注释的,但是它又是很特殊的
注释不会被编译器处理,直接原样复制忽略掉了,但注解可以被编译器打包进class
文件中,所以注解被理解为用作标注的元数据。
一般来说注解分为三类:
@Overrider
,这个基础注解直译就是覆写。@SuppressWarnings
,告诉编译器忽略此处的警告。
特点。通过上面两个小🌰可以发现,这一类的注解编译器使用而已,对于真实的代码跑起来后并不需要,因此这一类注解的特点就是:不会被编译进class
文件,编译后编译器就忽略掉这些代码了。
底层库处理时需要用到的注解,这类注解会被编译进class文件中
,但是距离我们一线开发者很遥远,目前不需要关注。
程序运行时需要读取并产生副作用的注解,这是我们一线开发者的需要经常使用的注解。
有了上面的小小的基础后,我们基本可以发现,注解可以在程序运行时告诉编译器,它有一些副作用,能帮助开发者做一些工作,而且写完之后到处使用,开发者仅仅需要打一个标签就行。
上面我们了解了注解的基本情况,大约有了点认识,接下来看一下java
官方的定义,毕竟要整点正规军的东西。
官方使用@interface
来定义一个新的注解,基本格式大约如下:
publice @interface Annotation { String value() default ""; // 多个参数... } 复制代码
default
默认值(虽然很多🐮的库也不这么干:-D)value
◔ ‸◔? ❓这还没理解注解,咋还冒出来个元注解
呢,因为这个是定义注解的第一步😄
所谓的元注解
就是:能够解释其他注解的注解,这样的注解我们就可以称呼它为meta annotation
。我们自定义注解需要用到一些重要的元注解
,下面介绍几个元注解:
@Target
这个注解告诉编译器我的代码在哪个位置被使用:
ElementType.TYPE
ElementType.FIELD
ElementType.METHOD
ElementType.CONSTRUCTURE
ElementType.PARAMETER
一个小🌰,假如你要定义一个用在方法
上的注解,那么就使用@Target(ElementType.METHOD)
@Target(ElementType.METHOD) public @interface Annotation { String value() default ""; } 复制代码
假如你要想定义一个注解用在方法或者字段上
的注解,可以使用@Target({ElementType.METHOD, ElementType.FIELD})
@Target({ ElementType.METHOD, ElementType.FIELD }) public @interface Annotation { String value() default ""; } 复制代码
@Retention
这个元注解极其重要
,它定义了注解的生命周期,即自定义的注解在代码的什么阶段被使用。RetentionPolicy.SOURCE
RetentionPolicy.CLASS
RetentionPolicy.RUNTIME
当然了,如果你一不小心忘了使用这个元注解,那么默认为CLASS
。在我们开发中,我们自定义的注解都是RUNTIME
的元注解。
@Retention(RetentionPolicy.RUNTIME) public @interface Annotation { String value() default ""; } 复制代码
@Repeatable
这个元注解是说自定义的注解可否被重复使用。一线开发比较少用。@Inherited
这个元注解是说子类可否继承父类定义的注解,但是它只能对@Target(ElementType.TYPE)
类型的注解生效,而且只是针对class
综上所述,自定义注解时,最重要的就是必须设置@Target @Retention
,以上一节的mybatis
中的基础注解@Select
为例:
RUNTIME
,适用范围在METHOD
上,另一个元注解就比较陌生啦。
所以啊o_O,java
中注解千千万,以后遇到陌生注解再说,目前够用(〃'▽'〃)……
在实战中使用,在模拟中练习
是最好的学习方式,本节尝试手写一个自定义注解去体会体会注解的奥妙,不过再开始写BUGS
之前,还有一些理论知识需要补充:
上一节注解的定义
中解释了@Retentions
元注解能够规定注解的三个生命周期,那个这三个生命周期要干啥呢:
SOURCE
生命周期的注解编译期使用,也就是说我们只关心使用就行。CLASS
仅在build之后中的class
文件中存在,与我们一线开发关系也不大。RUNTIME
是我们经常要使用并且可以充分发挥我们程序员才智的阶段。对于前端来说,下面的知识很陌生(说得好像其他知识你不陌生一样🙂):
java
中build后都是class文件,注解继承自java.lang.annotation.Annotation
,至于如何读取注解,需要继续学习反射API
,这就是下一节需要补充的知识了,这一节我们假装😄会用了。
反射API
基本操作既然我们要读取Annotation
,一般有以下几个步骤:
Class.isAnnotationPresent(Class)
Field.isAnnotationPresent(Class)
Method.isAnnotationPresent(Class)
Constructor.isAnnotationPresent(Class)
🌰://判断@Test注解是否存在与Test中 Test.class.isAnnotationPresent(Class) 复制代码
Class.getAnnotation(Class)
Field.getAnnotation(Class)
Method.getAnnotation(Class)
Constructor.getAnnotation(Class)
🌰:@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Test { String value() default ""; } //获取定义在Demo类上的@Test注解 Test test = Demo.class.getAnnotation(Test.class) String value = test.value() //... 复制代码
有了上述的基础知识之后,我们开始练习一下,手写一个简单的注解,实现判断类中的字段的最大值最小值
@Range
package com.wushao; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.TYPE }) public @interface Range { int min() default 0; int max() default 255; } 复制代码
package com.wushao; @Range(min = 1) public class Person { //name这个字符串长度必须在1-20之间 @Range(min = 1, max = 20) public String name; //city这个字符串长度最大为10,有个默认最小值0 @Range(max = 10) public String city; @Range(min = 1, max = 10) public int age; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", city='" + city + '\'' + ", age=" + age + '}'; } public Person(String name, String city, int age) { this.name = name; this.city = city; this.age = age; } } 复制代码
Main
入口函数中简单的测试package com.wushao; import java.lang.reflect.Field; public class Main { public static void main(String[] args) { Person p1 = new Person("wushao", "Qingdao", 20); Person p2 = new Person("", "Shanghai", 0); Person p3 = new Person("gaoyuayuan", "Beijing", 199); Range range = Person.class.getAnnotation(Range.class); System.out.println("Person的注解:" + range); range.max(); for (Person p : new Person[] {p1, p2, p3}) { try { check(p); System.out.println("Person " + p + " checked ok."); } catch (IllegalArgumentException | ReflectiveOperationException e) { System.out.println("Person " + p + " checked failed: " + e); } } } // 类中其他方法必须使用static关键字修饰,并且抛出以下两个错误 static void check(Person person) throws IllegalArgumentException, ReflectiveOperationException { //遍历person类中的所有字段 for (Field field: person.getClass().getFields()) { //获取定义在Field中的注解`@Range` Range range = field.getAnnotation(Range.class); //如果存在这个注解进行操作 if (range != null) { //获取不同Field字段的值 Object value = field.get(person); //TODO: 核心判断逻辑 } } } } 复制代码
上面的TODO
中的校验函数是挺重要的
static void check(Person person) throws IllegalArgumentException, ReflectiveOperationException { for (Field field: person.getClass().getFields()) { Range range = field.getAnnotation(Range.class); if (range != null) { Object value = field.get(person); // 判断字段值是否是String类型的 if (value instanceof String) { String s = (String) value; System.out.println("s: "+ s); //如果字段的值不符合注解的最大最小值抛出一个异常,会被`Main`函数的catch🐖 if (s.length() < range.min() || s.length() > range.max()) { throw new IllegalArgumentException("Invalid field is: " + field.getName()); } } } } } 复制代码
简单的执行一下,上面的demo实例发现打印如下:
发现在检验到p2
这个人的时候,报错了,因为他的name为空,长度不满足注解要求的1-20之间。
学习过程中感谢廖雪峰和菜鸟教程