全部知识体系文章,GitHub已收录,欢迎Star!再次感谢,愿你早日进入大厂!java
GitHub地址: https://github.com/Ziphtracks/JavaLearningmanualmysql
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及之后版本引入的一个特性,与类、接口、枚举是在同一个层次。它能够声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。git
- 编写文档: 经过代码里标识的元数据生成文档【生成文档doc文档】
- 代码分析: 经过代码里标识的元数据对代码进行分析【使用反射】
- 编译检查: 经过代码里标识的元数据让编译器可以实现基本的编译检查【Override等】
编写文档程序员
首先,咱们要知道Java中是有三种注释的,分别为单行注释、多行注释和文档注释。而文档注释中,也有@开头的元注解,这就是基于文档注释的注解。咱们可使用javadoc命令来生成doc文档,此时咱们文档的内元注解也会生成对应的文档内容。这就是编写文档的做用。github
代码分析web
咱们频繁使用之一,也是包括使用反射来经过代码里标识的元数据对代码进行分析的,此内容咱们在后续展开讲解。sql
编译检查数据库
至于在编译期间在代码中标识的注解,能够用来作特定的编译检查,它能够在编译期间就检查出“你是否按规定办事”,若是不按照注解规定办事的话,就会在编译期间飘红报错,并予以提示信息。能够就能够为咱们代码提供了一种规范制约,避免咱们后续在代码中处理太多的代码以及功能的规范。好比,@Override注解是在咱们覆盖父类(父接口)方法时出现的,这证实咱们覆盖方法是继承于父类(父接口)的方法,若是该方法稍加改变就会报错;@FunctionInterface注解是在编译期检查是不是函数式接口的,若是不遵循它的规范,一样也会报错。api
- @Override: 标记在成员方法上,用于标识当前方法是重写父类(父接口)方法,编译器在对该方法进行编译时会检查是否符合重写规则,若是不符合,编译报错。
- @Deprecated: 用于标记当前类、成员变量、成员方法或者构造方法过期若是开发者调用了被标记为过期的方法,编译器在编译期进行警告。
- @SuppressWarnings: 压制警告注解,可放置在类和方法上,该注解的做用是阻止编译器发出某些警告信息。
标记在成员方法上,用于标识当前方法是重写父类(父接口)方法,编译器在对该方法进行编译时会检查是否符合重写规则,若是不符合,编译报错。数组
这里解释一下@Override注解,在咱们的Object基类中有一个方法是toString方法,咱们一般在实体类中去重写此方法来达到打印对象信息的效果,这时候也会发现重写的toString方法上方就有一个@Override注解。以下所示:
因而,咱们试图去改变重写后的toString方法名称,将方法名改成toStrings。你会发如今编译期就报错了!以下所示:
那么这说明什么呢?这就说明该方法不是咱们重写其父类(Object)的方法。这就是@Override注解的做用。
用于标记当前类、成员变量、成员方法或者构造方法过期若是开发者调用了被标记为过期的方法,编译器在编译期进行警告。
咱们解释@Deprecated注解就须要模拟一种场景了。假设咱们公司的产品,目前是V1.0版本,它为用户提供了show1方法的功能。这时候咱们为产品的show1方法的功能又进行了扩展,打算发布V2.0版本。可是,咱们V1.0版本的产品须要抛弃吗?也就是说咱们V1.0的产品功能还继续让用户使用吗?答案确定是不能抛弃的,由于有一部分用户是一直用V1.0版本的。若是抛弃了该版本会损失不少的用户量,因此咱们不能抛弃该版本。这时候,咱们对功能进行了扩展后,发布了V2.0版本,咱们给予用户的通知就能够了,也就是告知用户咱们在V2.0版本中为功能进行了扩展。可让用户自行选择版本。
可是,除了发布告知用户版本状况以外,咱们还须要在原来版本的功能上给予提示,在上面的模拟场景中咱们须要在show1方法上方加@Deprecated注解给予提示。经过这种方式也告知用户“这是旧版本时候的功能了,咱们不建议再继续使用旧版本的功能”,这句话的意思也就正是给用户作了提示。用户也会这么想“奥,这版本的这个功能很差用了,确定有新版本,又更好用的功能。我要去官网查一下下载新版本”,还会有用户这么想“我明白了,又更新出更好的功能了,可是这个版本的功能我已经够用了,不须要从新下载新版本了”。
那么咱们怎么查看我上述所说的在功能上给予的提示呢?这时候我须要去建立一个方法,而后去调用show1方法,并查看调用时它是如何提示的。
图已经贴出来了,你是否发现的新旧版本功能的异同点呢?很明显,在方法中的提示是在调用的方法名上加了一道横线把该方法划掉了。这就体现了show1方法过期了,已经不建议使用了,咱们为你提供了更好的。
回想起来,在咱们的api中也会有方法是过期的,好比咱们的Date日期类中的方法有不少都已通过时了。以下图:
如你所见,是否是有不少方法都过期了呢?那它的方法上是加了@Deprecated注解吗?来跟着个人脚步,我带大家看一下。
咱们已经知道的Date类中的这些方法已是过期的了,若是咱们使用该方法并执行该程序的话。执行的过程当中就会提示该方法已过期的内容,可是只是提示,并不影响你使用该方法。以下:
OK!这也就是@Deprecated注解的做用了。
压制警告注解,可放置在类和方法上,该注解的做用是阻止编译器发出某些警告信息,该注解为单值注解,只有 一个value参数,该参数为字符串数组类型,参数值经常使用的有以下几个。
- unchecked:未检查的转化,如集合没有指定类型还添加元素
- unused:未使用的变量
- resource:有泛型未指定类型
- path:在类路径,原文件路径中有不存在的路径
- deprecation:使用了某些不同意使用的类和方法
- fallthrough:switch语句执行到底没有break关键字
- rawtypes:没有写泛型,好比: List list = new ArrayList();
- all:所有类型的警告
压制警告注解,顾名思义就是压制警告的出现。咱们都知道,在Java代码的编写过程当中,是有不少黄色警告出现的。可是我不知道你的导师是否教过你,程序员只须要处理红色的error,不须要理会黄色的warning。若是你的导师说过此问题,那是有缘由的。由于在你学习阶段,咱们认清处理红色的error便可,这样能够减轻你学习阶段在脑部的记忆内容。若是你刚刚加入学习Java的队列中,须要大脑记忆的东西就有太多了,也就是咱们目前不须要额外记忆其余的东西,只记忆重点便可。至于黄色warning嘛,在你的学习过程当中慢慢就会有所了解的,而不是死记硬背的。
那为了解释@SuppressWarnings注解,咱们还使用上一个例子,由于在那个例子中就有黄色的warning出现。
而每个黄色的warning都会有警告信息的。好比,这一个图中的警告信息,就告知你show2()方法没有被使用,简单来讲,你建立的show2方法,可是你在代码中并无调用过此方法。之后你便会遇到各类各样黄色的warning。而后, 咱们就可使用不一样的注解参数来压制不一样的注解。可是在该注解的参数中,提供了一个all参数能够压制所有类型的警告。而这个注解是须要加到类的上方,并赋予all参数,便可压制全部警告。以下:
咱们加入注解并赋予all参数后,你会发现use方法和show2方法的警告没有了,实际上导Date包的警告还在,由于咱们Date包导入到了该类中,可是咱们并无建立Date对象,也就是并无写入Date在代码中,你也会发现那一行是灰色的,也就证实了咱们没有去使用导入这个包的任何信息的说法,出现这种状况咱们就须要把这个没有用的导包内容删除掉,使用Ctrl + X
删除导入没有用到的包便可。还有一种办法就是在包的上方修饰压制警告注解,可是我认为在一个没有用的包上加压制注解是毫无心义的,因此,咱们直接删除就好。
而后,咱们还见到上图,注解那一行出现了警告信息提示。这一行的意思是冗余的警告压制。这就是说咱们压制如下的警告并无什么意义而形成的冗余,可是若是咱们使用了该类并作了点什么的话,压制注解的冗余警告就会消失,毕竟咱们使用了该类,此时就不会早场冗余了。
上述解释@SuppressWarnings注解也差很少就这些了。OK,继续向下看吧。持续为你们讲解。
@Repeatable 代表标记的注解能够屡次应用于相同的声明或类型,此注解由Java8版本引入。咱们知道注解是不能重复定义的,其实该注解就是一个语法糖,它能够重复多此使用,更适用于咱们的特殊场景。
首先,咱们先建立一个能够重复使用的注解。
package com.mylifes1110.anno;
import java.lang.annotation.Repeatable;
@Repeatable(Hour.class)
public @interface Hours {
double[] hours() default 0;
}
你会发现注解要求传入的值是一个类对象,此类对象就须要传入另一个注解,这里也就是另一个注解容器的类对象。咱们去建立一下。
package com.mylifes1110.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//容器
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Hour {
Hours[] value();
}
其实,这两个注解的套用,就是将一个普通的注解封装了一个可重复使用的注解,来达到注解的复用性。最后,咱们建立一下测试类,随后带你去看一下源码。
package com.mylifes1110.java;
import com.mylifes1110.anno.Hours;
@Hours(hours = 4)
@Hours(hours = 4.5)
@Hours(hours = 2)
public class Worker {
public static void main(String[] args) {
//经过Hours注解类型来获取Worker中的值数组对象
Hours[] hours = Worker.class.getAnnotationsByType(Hours.class);
//遍历数组
for (Hours h : hours) {
System.out.println(h);
}
}
}
测试类,是一个工人测试类,该工人使用注解记录早中晚的工做时间。测试结果以下:
而后咱们进入到源码一探究竟。
咱们发现进入到源码后,就只看见一个返回值为类对象的抽象方法。这也就验证了该注解只是一个可实现重复性注解的语法糖而已。
注解能够根据注解参数分为三大类:
- 标记注解: 没有参数的注解,仅用自身的存在与否为程序提供信息,如@Override注解,该注解没有参数,用于表示当前方法为重写方法。
- 单值注解: 只有一个参数的注解,若是该参数的名字为value,那么能够省略参数名,如 @SuppressWarnings(value = "all"),能够简写为@SuppressWarnings("all")。
- 完整注解: 有多个参数的注解。
说到@Override注解是一个标记注解,那咱们进入到该注解的源码查看一下。从上往下看该注解源码,发现它继承了导入了
java.lang.annotation.*
,也就是有使用到该包的内容。而后下面就又是两个看不懂的注解,其实发现注解的定义格式是public修饰的@Interface,最终看到该注解中方法体并无任何参数,也就是只起到标记做用。
在上面咱们用到的@SuppressWarnings注解就是一个单值注解。那咱们进入到它的源码看一下是怎么个状况。其实,和标记注解比较,它就多一个value参数而已,而这就是单值注解的必要条件,即只有一个参数。而且这一个参数为value时,咱们能够省略value。
上述两个类型注解讲解完,至于完整注解嘛,这下就能更明白了。其中的方法体就是有多个参数而已。
格式: public @Interface 注解名 {属性列表/无属性}
注意: 若是注解体中无任何属性,其本质就是标记注解。可是与其标注注解还少了上边修饰的元注解。
以下,这就是一个注解。可是它与jdk自定义注解有点区别,jdk自定义注解的上方还有注解来修饰该注解,而那注解就叫作元注解。元注解我会在后面详细的说到。
这里咱们的确不知道@Interface是什么,那咱们就把自定义的这个注解反编译一下,看一下反编译信息。反编译操做以下:
反编译后的反编译内容以下:
public interface com.mylifes1110.anno.MyAnno extends java.lang.annotation.Annotation {
}
首先,看过反编译内容后,咱们能够直观的得知他是一个接口,由于它的public修饰符后面的关键字是interface。
其次,咱们发现MyAnno
这个接口是继承了java.lang.annotation
包下的Annotation
接口。
因此,咱们能够得知注解的本质就是一个接口,该接口默认继承了Annotation
接口。
既然,是继承的Annotation
接口,那咱们就去进入到这个接口中,看它定义了什么。如下是我抽取出来的接口内容。咱们发现它看似很常见,其实它们不是很经常使用,做为了解便可。
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
最后,咱们的注解中也是能够写有属性的,它的属性不一样于普通的属性,它的属性是抽象方法。既然注解也是一个接口,那么咱们能够说接口体中能够定义什么,它一样也能够定义,而它的修饰符与接口同样,也是默认被public abstract
修饰。
而注解体中的属性也是有要求的。其属性要求以下:
- 属性的返回值类型必须是如下几种:
- 基本数据类型
- String类型
- 枚举类型
- 注解
- 以上类型的数组
- 注意: 在这里不能有void的无返回值类型和以上类型之外的类型
- 定义的属性,在使用时须要给注解中的属性赋值
- 若是定义属性时,使用
default
关键字给属性默认初始化值,则使用注解时能够不为属性赋值,它取的是默认值。若是为它再次传入值,那么就发生了对原值的覆盖。- 若是只有一个属性须要赋值,而且属性的名称为value,则赋值时value能够省略,能够直接定义值
- 数组赋值时,值使用
{}
存储值。若是数组中只有一个值,则能够省略{}
。
属性返回值既然有以上几种,那么我就在这里写出这几种演示一下是如何写的。
首先,定义一个枚举类和另一个注解备用。
package com.mylifes1110.enums;
public enum Lamp {
RED, GREEN, YELLOW
}
package com.mylifes1110.anno;
public @interface MyAnno2 {
}
其次,咱们来定义上述几种类型,以下:
package com.mylifes1110.anno;
import com.mylifes1110.enums.Lamp;
public @interface MyAnno {
//基本数据类型
int num();
//String类型
String value();
//枚举类型
Lamp lamp();
//注解类型
MyAnno2 myAnno2();
//以上类型的数组
String[] values();
Lamp[] lamps();
MyAnno2[] myAnno2s();
int[] nums();
}
这里咱们演示一下,首先,咱们使用该注解来进行演示。
package com.mylifes1110.anno;
public @interface MyAnno {
//基本数据类型
int num();
//String类型
String value();
}
随后建立一个测试类,在类的上方写上注解,你会发现,注解的参数中会让你写这两个参数(int、String)。
此时,传参是这样来作的。格式为:名称 = 返回值类型参数
。以下:
上述所说,若是使用default关键字给属性默认初始化值,就不须要为其参数赋值,若是赋值的话,就把默认初始化的值覆盖掉了。
固然还有一个规则,若是只有一个属性须要赋值,而且属性的名称为value,则赋值时value能够省略,能够直接定义值。那么,咱们的num已经有了默认值,就能够不为它传值。咱们发现,注解中定义的属性就剩下了一个value属性值,那么咱们就能够来演示这个规则了。
这里,我并无写属性名称value,而是直接为value赋值。若是我将num的default关键字修饰去掉呢,那意思也就是说在使用该注解时必须为num赋值,这样能够省略value吗?那咱们看一下。
结果,就是咱们所想的,它报错了,必须让咱们给num赋值。其实想一想这个规则也是很容易懂的,定义一个为value的值,就能够省略其value名称。若是定义多个值,它们能够省略名称就没法区分定义的是那个值了,关键是还有数组,数组内定义的是多个值呢,对吧。
这里咱们演示一下,上述的多种返回值类型是如何赋值的。这里咱们定义这几个参数来看一下,是如何为属性赋值的。
num是一个int基本数据类型,即num = 1
value是一个String类型,即value = "str"
lamp是一个枚举类型,即lamp = Lamp.RED
myAnno2是一个注解类型,即myAnno2 = @MyAnno2
values是一个String类型数组,即values = {"s1", "s2", "s3"}
values是一个String类型数组,其数组中只有一个值,即values = "s4"
注意: 值与值之间是,
隔开的;数组是用{}
来存储值的,若是数组中只有一个值能够省略{}
;枚举类型是枚举名.枚举值
元注解就是用来描述注解的注解。通常使用元注解来限制自定义注解的使用范围、生命周期等等。
而在jdk的中java.lang.annotation包中定义了四个元注解,以下:
元注解 | 描述 |
---|---|
@Target | 指定被修饰的注解的做用范围 |
@Retention | 指定了被修饰的注解的生命周期 |
@Documented | 指定了被修饰的注解是能够Javadoc等工具文档化 |
@Inherited | 指定了被修饰的注解修饰程序元素的时候是能够被子类继承的 |
@Target 指定被修饰的注解的做用范围。其做用范围能够在源码中找到参数值。
属性 | 描述 |
---|---|
CONSTRUCTOR | 用于描述构造器 |
FIELD(经常使用) | 用于描述属性 |
LOCAL_VARIABLE | 用于描述局部变量 |
METHOD(经常使用) | 用于描述方法 |
PACKAGE | 用于描述包 |
PARAMETER | 用于描述参数 |
TYPE(经常使用) | 用于描述类、接口(包括注解类型) 或enum声明 |
ANNOTATION_TYPE | 用于描述注解类型 |
TYPE_USE | 用于描述使用类型 |
因而可知,该注解体内只有一个value属性值,可是它的类型是一个ElementType数组。那咱们进入到这个数组中继续查看。
进入到该数组中,你会发现他是一个枚举类,其中定义了上述表格中的各个属性。
了解了@Target的做用和属性值后,咱们来使用一下该注解。首先,咱们要先用该注解来修饰一个自定义注解,定义该注解的指定做用在类上。以下:
而你观察以下测试类,咱们把注解做用在类上时是没有错误的。而当咱们的注解做用在其余地方就会报错。这也就说明了,咱们@Target的属性起了做用。
注意: 若是咱们定义多个做用范围时,也是能够省略该参数名称了,由于该类型是一个数组,虽然能省略名称可是,咱们还须要用{}
来存储。
@Retention 指定了被修饰的注解的生命周期
属性 | 描述 |
---|---|
RetentionPolicy.SOURCE | 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 |
RetentionPolicy.CLASS | 注解只被保留到编译进行时的class文件,但 JVM 加载class文件时候被遗弃,也就是在这个阶段不会读取到该class文件。 |
RetentionPolicy.RUNTIME(经常使用) | 注解能够保留到程序运行的时候,它会被加载进入到 JVM 中,因此在程序运行时能够获取到它们。 |
注意: 咱们经常使用的定义便是RetentionPolicy.RUNTIME
,由于咱们使用反射来实现的时候是须要从JVM中获取class类对象并操做类对象的。
首先,咱们要了解反射的三个生命周期阶段,这部份内容我在Java反射机制
中也是作了很是详细的说明,有兴趣的小伙伴能够去看看我写的Java反射机制,相信你在其中也会有所收获。
这里我再次强调一下这三个生命周期是源码阶段
- > class类对象阶段
- > Runtime运行时阶段
。
那咱们进入到源码,看看@Retention注解中是否有这些参数。
咱们看到该注解中的属性只有一个value,而它的类型是一个RetentionPolicy类型,咱们进入到该类型中看看有什么参数,是否与表格中的参数相同呢?
至于该注解怎么使用,实际上是相同的,用法以下:
这就证实了咱们的注解能够保留到Runtime运行阶段,而咱们在反射中大多数是定义到Runtime运行时阶段的,由于咱们须要从JVM中获取class类对象并操做类对象。
@Documented 指定了被修饰的注解是能够Javadoc等工具文档化
@Documented注解是比较好理解的,它是一个标记注解。被该标记注解标记的注解,生成doc文档时,注解是能够被加载到文档中显示的。
还拿api中过期的Date中的方法来讲,在api中显示Date中的getYear方法是这样的。
正如你看到的,注解在api中显示了出来,证实该注解是@Documented注解修饰并文档化的。那咱们就看看这个注解是否被@Documented修饰吧。
而后,咱们发现该注解的确是被文档化了。因此在api中才会显示该注解的。若是不信,你能够本身使用javadoc
命令来生成一下doc文档,看看被该注解修饰的注解是否存在。
至于Javadoc文档生成,我在javadoc文档生成一文中有过详细记载,你们能够进行参考,生成doc文档查看。
@Inherited 指定了被修饰的注解修饰程序元素的时候是能够被子类继承的
首先进入到源码中,咱们也能够清楚的知道,该注解也是一个标记注解。并且它也是被文档化的注解。
其次,咱们去在自定义注解中,标注上@Inherited注解。
演示@Inherited注解,我须要建立两个类,同时两个类中有一层的继承关系。以下:
咱们在Person类中标记了@MyAnno注解,因为该注解被@Inherited注解修饰,咱们就能够得出继承于Person类的Student类也一样被@MyAnno注解标记了,若是你要获取该注解的值的话,确定获取的也是父类上注解值的那个"1"。
自定义注解
package com.mylifes1110.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @InterfaceName Sign
* @Description 描述须要执行的类名和方法名
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sign {
String methodName();
String className();
}
Cat
package com.mylifes1110.java;
/**
* @ClassName Cat
* @Description 描述一只猫的类
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
public class Cat {
/*
* @Description 描述一只猫吃鱼的方法
* @Author Ziph
* @Date 2020/6/6
* @Param []
* @return void
*/
public void eat() {
System.out.println("猫吃鱼");
}
}
准备好,上述代码后,咱们就能够开始编写使用反射技术来解析注解的测试类。以下:
首先,咱们先经过反射来获取注解中的methodName和className参数。
package com.mylifes1110.java;
import com.mylifes1110.anno.Sign;
/**
* @ClassName SignTest
* @Description 要求建立cat对象并执行其类中eat方法
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
@Sign(className = "com.mylifes1110.java.Cat", methodName = "eat")
public class SignTest {
public static void main(String[] args) {
//获取该类的类对象
Class<SignTest> signTestClass = SignTest.class;
//获取类对象中的注解对象
//原理其实是在内存中生成了一个注解接口的子类实现对象
Sign sign = signTestClass.getAnnotation(Sign.class);
//调用注解对象中定义的抽象方法(注解中的属性)来获取返回值
String className = sign.className();
String methodName = sign.methodName();
System.out.println(className);
System.out.println(methodName);
}
}
此时的打印结果证实咱们已经成功获取到了该注解的两个参数。
注意: 获取类对象中的注解对象时,其原理其实是在内存中生成了一个注解接口的子类实现对象并返回的字符串内容。以下:
public class SignImpl implements Sign {
public String methodName() {
return "eat";
}
public String className() {
return "com.mylifes1110.java.Cat";
}
}
继续编写咱们后面的代码,代码完整版以下:
完整版代码
package com.mylifes1110.java;
import com.mylifes1110.anno.Sign;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @ClassName SignTest
* @Description 要求建立cat对象并执行其类中eat方法
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
@Sign(className = "com.mylifes1110.java.Cat", methodName = "eat")
public class SignTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//获取该类的类对象
Class<SignTest> signTestClass = SignTest.class;
//获取类对象中的注解对象
//原理其实是在内存中生成了一个注解接口的子类实现对象
Sign sign = signTestClass.getAnnotation(Sign.class);
//调用注解对象中定义的抽象方法(注解中的属性)来获取返回值
String className = sign.className();
String methodName = sign.methodName();
//获取className名称的类对象
Class<?> clazz = Class.forName(className);
//建立对象
Object o = clazz.newInstance();
//获取methodName名称的方法对象
Method method = clazz.getMethod(methodName);
//执行该方法
method.invoke(o);
}
}
执行结果
执行后成功的调用了eat方法,并打印了猫吃鱼的结果,以下:
首先,咱们在使用JDBC的时候是须要经过properties文件来获取配置JDBC的配置信息的,此次咱们经过自定义注解来获取配置信息。其实使用注解并无用配置文件好,可是咱们须要了解这是怎么作的,获取方法也是鱼使用反射机制解析注解,所谓“万变不离其宗”,它就是这样的。
package com.mylifes1110.java.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @InterfaceName DBInfo
* @Description 给予注解声明周期为运行时并限定注解只能用在类上
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBInfo {
String driver() default "com.mysql.jdbc.Driver";
String url() default "jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf8";
String username() default "root";
String password() default "123456";
}
为了代码的健全我也在里面加了properties文件获取链接的方式。
package com.mylifes1110.java.utils;
import com.mylifes1110.java.anno.DBInfo;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* @ClassName DBUtils
* @Description 数据库链接工具类
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
@DBInfo()
public class DBUtils {
private static final Properties PROPERTIES = new Properties();
private static String driver;
private static String url;
private static String username;
private static String password;
static {
Class<DBUtils> dbUtilsClass = DBUtils.class;
boolean annotationPresent = dbUtilsClass.isAnnotationPresent(DBInfo.class);
if (annotationPresent) {
/**
* DBUilts类上有DBInfo注解,并获取该注解
*/
DBInfo dbInfo = dbUtilsClass.getAnnotation(DBInfo.class);
// System.out.println(dbInfo);
driver = dbInfo.driver();
url = dbInfo.url();
username = dbInfo.username();
password = dbInfo.password();
} else {
InputStream inputStream = DBUtils.class.getResourceAsStream("db.properties");
try {
PROPERTIES.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, username, password);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
resultSet = null;
}
if (statement != null) {
statement.close();
statement = null;
}
if (connection != null) {
connection.close();
connection = null;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
package com.mylifes1110.java.test;
import com.mylifes1110.java.utils.DBUtils;
import java.sql.Connection;
/**
* @ClassName GetConnectionDemo
* @Description 测试链接是否能够获取到
* @Author Ziph
* @Date 2020/6/6
* @Since 1.8
*/
public class GetConnectionDemo {
public static void main(String[] args) {
Connection connection = DBUtils.getConnection();
System.out.println(connection);
}
}
为了证实获取的链接是由注解的配置信息获取到的链接,我将properties文件中的全部配置信息删除后测试的。
我不清楚小伙伴们是否了解,Junit单元测试。@Test是单元测试的测试方法上方修饰的注解。此注解的核心原理也是由反射来实现的。若是有小伙伴不知道什么是单元测试或者对自定义@MyTest注解实现单元测试感兴趣的话,能够点进来看看哦!