原文链接 - Joker's
欢迎大家访问我的个人博客:)
大约有一个月没写博啦,中间经历了急性支气管炎、大雪封山、懒癌附身种种,这次想写的是「注解」,一个之前一直很惧怕的东西。
起因是跟着【手把手】JavaWeb 入门级项目实战 -- 文章发布系统 (第四节)这篇博文在做入门的 jsp 项目,所以接触到了。接触到了,自然就要了解一下。部分内容出自原博客,侵删。
我是后端新手,以前完全没接触过后端项目,所以做的时候理解上会有问题,同时我也想把自己遇到的这些问题,用浅显易懂的方式说出来,可以供有相同疑惑的朋友参考。如有不足之处,欢迎指正。
这篇文章可能有点长,但写的应该比较容易看懂,如果你也是新手,希望你能看到最后,不明白的部分可以留言给我讨论。文章整体脉络应该是比较清晰:首先介绍注解是什么
,然后再看注解的定义
,最后是如何使用
,把这些讲完之后我又举了一个完整的实例
来讲解。
目录
Step 1. 注解是什么
首先介绍一下,注解到底是个啥?为什么用途这么广泛?
Step 2. 注解怎么写
初步了解以后,让我们直接上手写一个!
Step 3. 注解如何用
写完注解不会用相当于没有写,那么我们如何使用呢?
3.1 给谁用?
3.2 怎么用?
3.3 几个注意点
Step 4. 看一个完整的例子:获取数据库表名
从零开始,展示一个完整的注解用法。
4.1 第 0-1 步:给谁用?(JavaBean)
4.2 第 0-2 步:注解怎么写(定义注解)
4.3 第 1 步:获取类的对象
4.4 第 2 步:获得相应的方法/域
4.5 第 3 步:获取注解的实例
4.6 第 4 步:获取对应的属性
参考
正文
Step 1. 注解是什么
原博主写了一篇文章非常好,推荐仔细看一遍:用大白话聊聊JavaSE -- 自定义注解入门。我看了三遍,才感觉理解个差不多。
我简单总结一下原博主的思想:注解就像是写给电脑看的注释,有了注解,我们可以通过代码来获得关于一个方法或者一个类的信息。在平时的开发过程中,我们会给一个类、方法或者域添加注释,比如,我们会在方法前面添加关于入参、返回值、以及方法作用的注释。同样的,我们可以给一个类、方法或者域添加注解。
同时,你仔细观察的话,会发现注解非常像一个空的接口。
了解了这些内容,我来介绍一下元注解
的概念。典型的元注解有 @Target
和 @Retention
,这是我们在定义一个注解时需要用到的,
@Target 用来定义你的注解用在什么地方:
-
@Target(ElementType.FIELD)
表示用于一个域 -
@Target(ElementType.METHOD)
表示用于一个方法 -
@Target(ElementType.TYPE)
则表示用于一个类。
@Retention 用来定义注解在哪一个级别可用:
-
@Retention(RetentionPolicy.SOURCE)
表示在源代码中可用 -
@Retention(RetentionPolicy.CLASS)
表示在类文件中可用 -
@Retention(RetentionPolicy.RUNTIME)
表示在运行时可用,一般用的比较多。
Step 2. 注解怎么写
下面让我们来看一下注解的格式,我们参照原作者的方式,来给方法写一个注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnnotation {
// 方法作用,有默认值
String description() default "作者很懒,没有写本方法的作用";
// 方法创建时间
String createTime();
}
很显然,我们可以通过这个注解来获取方法的作用和创建时间,当然,我们也可以加上作者,无非是再加这么一行:String author() default "Joker";
。此外,在定义注解的过程中,默认值是可选的。
Step 3. 注解如何用
好,通过上面两部分,我们已经了解了注解是什么,以及如何写一个简单的注解,接下来看一看注解是怎么用的。这要分为两部分来说。
3.1 给谁用?
既然明白了我们刚刚编写的注解时用来写给方法的,那么我们想获得哪个方法的作用和创建时间呢?
让我们来随便写一个:
package util;
import java.util.Date;
import java.text.SimpleDateFormat;
public class DateUtil {
@MethodAnnotation(createTime = "2018-01-01")
public static String formatDate(Date date , String formatPattern){
return new SimpleDateFormat(formatPattern).format(date);
}
}
现在目标明确了,我们是想获得 formatDate
这个方法的作用和创建时间,那就先给方法加上我们刚刚创建的注解,也就是@MethodAnnotation()
。括号里面要填入注解中对应的参数和值,有默认值的可以不用写。如果参数没有默认值,且在这里没有填写的话(比如在这里只写一个空括号),ide 会报 The annotion @MethodAnnotation must define the attribute createTime
错误。
3.2 怎么用?
计算机如何在 Java 类中获取我们上面定义的注解信息呢?
那我们不得不聊聊另外一个名词:反射
。我们先来看一个例子(这里我还是使用原作者的例子,侵删),在 DateUtil.java 中创建一个单元测试(main 方法)如下:
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Class classOfDateUtil = DateUtil.class;
Method formatDate = classOfDateUtil.getMethod("formatDate", Date.class,String.class);
MethodAnnotation methodNote = formatDate.getAnnotation(MethodAnnotation.class);
System.out.println("方法描述:" + methodNote.description());
System.out.println("创建日期:" + methodNote.createTime());
}
首先读一下上面的代码,自己想一想,然后再来看我的总结。
---------------------------------------------------分割线---------------------------------------------------
关于上面的反射过程,我个人总结为四部曲:
- 获取类的对象。如果在编译器能够知道名字的话,可以使用上面的方法;如果在编译器不知道类的名字,但是可以在运行期获得到类名字符串的话,可以使用以下方法:
String className = ... ;//在运行期获取的类名字符串
Class class = Class.forName(className);
- 根据类对象获得相应的方法/域,如果注解是加在类上的,可以省略这一步。主要方法有(具体用法可以自己去查询 API 文档):
- Field getField()
- Field[] getFields() 返回 Field 数组,包括这个类及其父类的公有域。
- Field[] getDeclaredFields() 返回 Field 数组,包括这个类的所有域。
- Method getMethod()
- Method[] getMethods() 返回所有的公有方法,包括从父类继承来的公有方法。
- Method[] getDeclaredMethods() 返回所有的方法,但不包括由父类继承来的方法。
- 根据获得的域/方法/类对象,结合 getAnnotations() 方法来获取注解的实例。
- 根据注解实例,获取对应的信息。结束!
3.3 几个注意点
- 不要漏写 default 值。
- 在第二步时,注意看下用到的方法是否抛出异常。如果有的话,在3中不要忘记抛出。
Step 4. 看一个完整的例子:获取数据库表名
这里的情景是,用户在登陆页面上输入了用户名和密码,点登陆的时候,发送请求到我们的 controller 层,此时我们把用户数据封装为一个 JavaBean,再把 JavaBean 转化为建表语句。为了简化过程,最后一步我省略掉,我们只看怎么获取表名即可。
关于 JavaBean 大家可以看看原博主的这篇文章:用大白话聊聊JavaSE -- 如何理解Java Bean(一),写的非常好。
我简单解释一下 JavaBean 和数据库的对应关系:写 JavaBean 的过程其实就相当于是数据库的设计过程,类
对应着表
,属性
对应着字段
。
4.1 第 0-1 步:给谁用?(JavaBean)
既然是用户,我们新建一个 User 类,我只抽了一部分。
public class User {
private String username;
private String password;
...
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
}
4.2 第 0-2 步:注解怎么写(定义注解)
我们新建一个 Annotation 文件,就叫 Table。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
public String tableName();
}
这里把导包过程省略了,请大家自行补充。
这步做完之后,我们给 0-1 中的 User 类加上注解,参数设置为 t_user,即表名:
@Table(tableName = "t_user")
public class User {
}
4.3 第 1 步:获取类的对象
这里我们封装一个 TableUtils 作为数据库表的工具类,来使用注解,真正的调用过程我们在单元测试中进行。在刚刚讲解的过程中,其实是把这两部分合二为一了。在实际开发过程中,可能要将不同的模块分包处理,比如 annotions 包、util 包、bean 包等等。
// 作为工具类API使用,直接传入类的对象
public class TableUtils {
public static String getCreateTablename(Class<?> clazz) {
}
}
// 在这里传入类
public class TestMain {
public static void main(String[] args) {
TableUtils.getCreateTablename(User.class);
}
}
4.4 第 2 步:获得相应的方法/域
因为我们的注解是加在类上的,所以这一步可以省略。
4.5 第 3 步:获取注解的实例
既然是获取注解的实例,肯定是在直接使用注解的地方,也就是 TableUtils 中进行。
public static String getCreateTablename(Class<?> clazz) {
Table table = clazz.getAnnotation(Table.class);
}
4.6 第 4 步:获取对应的属性
这里我们要获取表名,可以控制台打印下,看看是不是我们的 t_user
。
public static String getCreateTablename(Class<?> clazz) {
Table table = clazz.getAnnotation(Table.class);
String tableName = table.tableName();
System.out.println(tableName);
}
至此,一个完整的注解 demo 完成,感谢大家看到最后~
参考
- java 类 - 极客学院
- 【手把手】JavaWeb 入门级项目实战 -- 文章发布系统 (第四节)
- 用大白话聊聊JavaSE -- 自定义注解入门
- 创建 MySQL 表 - 极客学院
- 《Java 核心技术 5.7 反射》
- 《Java 编程思想 第20章 注解》