前言
在之前的文章 Marco’s Java 之【JDBC辅助类封装】 中我们有简单的提到过注解的使用方式,但是讲的不够详细,可能有的小伙伴看到注解的使用方式会有些懵,后续我们在使用各种框架的时候都会大量的使用到注解,因此,我这边专门写一篇博文详细的介绍一下什么是注解以及注解的用法。
什么是注解
Annotation可以解释为注解,也可以解释为注释,可能我说注释,大家会觉得好理解一些,注释就是对什么做解释的意思,那么Java中的Annotation是用来注释什么呢?
Annotation说白了其实也是Java中的一个类,大家新建文件的时候可以看到一个@符号标识的就是我们的注解了
我们来看看注解在度娘上的定义
定义: 注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
Annotation注释的其实就是就是我们Java中的类,由于它本身也是一个类,所以,它也可以注释注解本身,准确来说,它可以注释类、类的属性(变量、常量 和 方法),几乎涵盖我了我们平时写代码所使用到的类型。
再深入了解它之前先来缕一缕我们常见的一些Annotation吧~
Java中内置注解和元注解
Java内置注解
所谓内置注解,就是我们在使用Eclipse时,内置的java文件中自带注解
@Override : 表明 这个方法适用于 重写父类中方法
@Deprecated : 表明 被注释的对象,程序不推荐使用。
@SuppressWarnings:禁止显示警告
第一个相信大家都见过对吧,我们在重写toString()方法的时候,就会遇到@Override
@Deprecated这个相信大家也不会陌生,如果大家还是觉得没有见过,相信应该见过下面这种情况吧~
那我们查看getDate()源码发现上面就是@Deprecated注解
@SuppressWarnings这个注解主要是禁止显示警告,比如说我们创建了一个流对象BufferedInputStream(),如果没有关闭它,就会有一个黄色的标识,那么如果大家觉得碍眼,可以添加@SuppressWarnings
在这里我先打断一下,因为大家可能平时都或多或少接触过这些注解,但是不知道怎么用,或者说它到底有什么用?这里我们拿@Override来举个栗子,大家可能会发现,我们再重写toString()方法的时候使用了@Override注解,但是如果去掉@Override,程序仍然可以正常执行,那我要他有什么用呢?
其实这个时候@Override更趋近于一种规范,一种标记,代表你的这个方式是重写父类的方法的,除了这个还有别的好处吗?
Sure!事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器会报错,那么比如说我方法拼写错了,或者参数传的不对,那么编译器就会发现,这小子不对头,然后会报错,使用注解的好处不仅限于这,我们继续往下走
Java元注解
Java一共有5个元注解,分别是@Target @Retention @Inherited @Repeatable @Documented
什么是元注解呢?你可以理解为是最原始的注解,这些注解是可以加在我们自定义注解上的
@Target:目标 表示注解作用目标
- ElementType.ANNOTATIONTYPE : 表示这个注解可以用在注解上
- ElementType.CONSTRUCTOR : 表示这个注解可以用构造方法上
- ElementType.FIELD : 表示这个注解可以给属性进行注释
- ElementType.LOCALVARIABLE :表示这个注解可以给局部变量进行注释
- ElementType.METHOD: 表示可以给方法进行注释
- ElementType.PACKAGE: 表示可以给包注释
- ElementType.PARAMETER:表示可以给参数进行注释
- ElementType.TYPE 可以给一个类型进行注解 比如类、接口、枚举
@Retention:保留期 表示 注解生效的范围
- RetentionPolicy.SOURCE 注解只在源码阶段保留
- RetentionPolicy.CLASS 在编译文件中有效
- RetentionPolicy.RUNTIME 在运行时有效( 只有在运行时生效,才能通过反射获取注解信息 )
@Inherited: 表示该类的注解会被子类继承
@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{ }
@Documented: 一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中,表示会生成相关文档
一般情况下,我们使用的最多的就是@Target @Retention,合理的使用这两个注解会达到意想不到的效果~
在了解@Target @Retention之前我们先了解下注解中的属性:
1.在注解中只有属性,没有方法 。
2.在注解中,属性定义格式,类似于定义一个无参的方法,方法返回值是 属性类型,方法名称是属性名称。
3.在注解中,可以通过 default 关键,为属性赋默认值。
4.在注解中,若只有一个属性,则属性一般该属性使用value命名
5.在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组
自定义注解
在了解过JAVA本身自带的一些注解之后,我们来看看怎么自定义注解,毕竟属于自己的东西才是最好的,哈哈~
上面我们提到过的@Target @Retention在这个时候就要显威啦,虽然@Target @Retention中大家可以看又分很多种类,但是很多我们平时也没有用不到
就好比说@Retention中的 RetentionPolicy.SOURCE,这个时候注解只在源码阶段保留,咱们平时运行的程序,都是在JVM中编译运行完成之后展现出来的,所以说如果注解只在源码阶段才生效,那么在程序运行阶段我们根本就提取不到想要的值,或者根据没有办法使用它应有的功能
那么在这里我着重关注下@Target中的ElementType.FIELD,ElementType.METHOD,ElementType.TYPE以及
@Retention中的RetentionPolicy.RUNTIME
老样子,还是新建一个注解给大家示范一下效果
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
public @interface MyAnnotation {
public String name() default "marco";
public int age() default 18;
public String[] contactList() default {"sunnie","windy","jack"};
}
在这里,如果一个注解可能被多个类的属性使用,可以用中括号括起来,之前我们的Servlet项目中就有自定义的注解@WebServlet(urlPatterns = {"/userServlet.do","/user.do"})
相信大家已经见过了
主要注意的是注解中是不能定义方法的,那有的朋友会问了,这里出现的public String name()是什么鬼?
大家可以再回过头来看看我之前提到的注解中的属性咯。
在注解中,属性定义格式,类似于定义一个无参的方法,方法返回值是 属性类型,方法名称是属性名称
并且!属性后面可以加default,有没有觉得这个跟我们的数据库中的表的创建很像呢?我们随便来找一个比较下
是不是很相像,我猜大家应该隐隐的猜到用注解可以做什么了吧~ 我们继续,先来测试一下我们自定义的注解
@MyAnnotation
public class TestAnnotation {
public static void main(String[] args) {
Class clz = TestAnnotation.class;
Annotation[] annotations = clz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}
一点击运行。欸,发现好像什么都没有打印,有点不对头。明明我也添加了注解啊,仔细看看我们的自定义注解好像少了点什么。。
对,就是我们之前提到的@Retention不写作用域默认是RetentionPolicy.SOURCE 注解只在源码阶段保留
因此我们必须添加上@Retention(RetentionPolicy.RUNTIME),添加后我们再来运行一下
发现控制台打印了上面的一串数据,那么既然之前在注解中的数据都打印出来了,是不是有什么办法可以直接获取我们所定义的数据呢? 当然是No Problem的!
public static void main(String[] args) {
Class clz = TestAnnotation.class;
Annotation[] annotations = clz.getAnnotations();
for (Annotation annotation : annotations) {
MyAnnotation myAnno = (MyAnnotation)annotation;
System.out.println(myAnno.name());
System.out.println(myAnno.age());
String[] contactList = myAnno.contactList();
for (String contact : contactList) {
System.out.println(contact);
}
}
}
运行结果如下,是不是都给获取到了?需要注意的是annotation的类型是Annotation而不是我们自定义的MyAnnotation,因此注解的本质是一个继承了Annotation特殊接口,因此如果需要获取到里面的值需要先向下转型才能获取到
除了上面介绍的注解的方法,还有以下的方法等。
boolean flag = clz.isAnnotationPresent(TestAnnotation.class);//判断当前类是否包含指定注解(被注解注释)
MyAnnotation myAnno = (MyAnnotation)clz.getAnnotation(MyAnnotation.class);//如果知道注解名可以直接获取
MyAnnotation.class.isAnnotation();//判断这个类MyAnnotation是否是注解
那么到这里可能还有的小伙伴不知道,用注解到底可以做什么呢?
接下来我们来演示一个栗子
使用注解创建数据库表
首先我们建两个自定义注解@Table和@Column
/*类注解*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
public String tableName();
}
/*类属性注解*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public String name();
public String type();
public boolean isAutoIncrement() default false;
public boolean isKey() default false;
}
接下来我们新建一个Student类,先说一下我们需要完成的效果是什么样的
那么根据这个表我们就可以完善Student类,Student中的4个属性分别对应着t_student表中的四个字段
我们这里设置stuId为主键并且自增。
@Table(tableName = "t_student")
public class Student {
@Column(name = "stuid", type = "int", isAutoIncrement = true, isKey = true)
private int stuId;
@Column(name = "realname", type = "varchar")
private String realName;
@Column(name = "sex", type = "varchar")
private String sex;
@Column(name = "age", type = "int")
private int age;
}
好戏开始啦,运用我们目前的Annotation知识加上反射妥妥的可以创建数据库表啦,话不多说,上代码~
public class CreateTable {
public static void main(String[] args) {
/*
* create table student(
* stuid int(5) primary key auto_increment,
* realname varchar(20) not null,
* sex varchar(6) default 'MEN',
* age int(3) default 0
* )
* */
Class student = Student.class;//获取Student的class对象
Table table = (Table)student.getAnnotation(Table.class);//或者Student类的Table注解对象
String tableName = table.tableName();//通过Table注解对象获取表名
List<Map<String, Object>> columns = new ArrayList<Map<String,Object>>();
Field[] fields = student.getDeclaredFields();
for (Field field : fields) {
Column anno = field.getAnnotation(Column.class);
boolean isAutoIncrement = anno.isAutoIncrement();//是否自增
boolean isKey = anno.isKey();//是否为主键
String name = anno.name();//字段名
String type = anno.type();//字段的类型
Map<String,Object> columnMap = new HashMap<String, Object>();//将所有获取到的注解的属性和值置入Map容器
columnMap.put("isAutoIncrement", isAutoIncrement);
columnMap.put("isKey", isKey);
columnMap.put("name", name);
columnMap.put("type", type);
columns.add(columnMap);
}
StringBuilder createTableSql = new StringBuilder("create table " + tableName + " (" );
for (Map<String, Object> map : columns) {
boolean isAutoIncrement = (boolean) map.get("isAutoIncrement");
boolean isKey = (boolean) map.get("isKey");
String name = map.get("name") + "";
String type = map.get("type") + "";
createTableSql.append(" " + name + " " + type + "(20)");//这里的数值也可以在注解中添加属性和value值
if(isKey) {
createTableSql.append(" primary key");
}
if(isAutoIncrement) {
createTableSql.append(" auto_increment,");
} else {
createTableSql.append(",");
}
}
String sql = createTableSql.substring(0, createTableSql.length() -1 ) + ")";
System.out.println(sql);
}
}
使用注解封装JSON
之前我们在 Marco’s Java 之【JSON&AJAX】 中有讲到使用反射和原生JSON模拟Alibaba的fastjson工具,那这里我们刚好也有提到注解,并且通过上面的一个案例,大家对注解已经有所认知,我们可以再稍微做一点提升。
那问题是这样的,大家应该知道我们不同的语种的命名规范是不一样的,好比我们上面的数据库,或者PHP等等,假如说我们获取到的JSON字符串是这样的,我们在没有学习注解之前应该很难处理对吧
String jsonStr = "{user_id:1001,user_name:'marco',password:'123456'}";
今时不同往日,有了注解之后,两行代码解决问题,绝对不吹牛逼!
第一步,先创建自定义注解@JsonName
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonName {
public String name();
}
然后创建一个User类,其他部分我就省略了哈,在id和userName属性上添加注解并给到相应的值
@JsonName(name="user_id")
private int id;
@JsonName(name="user_name")
private String userName;
private String password;
接下来好戏就开始啦
public class JsonToObj {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, JSONException {
String jsonStr = "{user_id:1001,user_name:'marco',password:'123456'}";
User user = jsonToObj(jsonStr, User.class);
System.out.println(user);
}
@SuppressWarnings("unchecked")
public static <T> T jsonToObj(String jsonStr, Class<T> clz) throws JSONException, InstantiationException, IllegalAccessException {
JSONObject jsonObject = new JSONObject(jsonStr);
T obj = clz.newInstance();
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
JsonName jsonName = field.getAnnotation(JsonName.class);//获取属性上的注解
//这里需要注意,因为我们的password属性没有添加注解,所以需要在这里判断一下,该属性是否有注解,如果没有则使用原始名称
String fieldName = jsonName == null ? field.getName():jsonName.name();
String type = field.getType().getSimpleName();
if("String".equals(type)) {
field.set(obj, jsonObject.getString(fieldName));
} else if("int".equals(type)) {
field.set(obj, jsonObject.getInt(fieldName));
} else if("boolean".equals(type)) {
field.set(obj, jsonObject.getBoolean(fieldName));
} else if("List".equals(type)) {
JSONArray jsonArray = jsonObject.getJSONArray(fieldName);
List<T> list = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
list.add((T) jsonArray.get(i));
}
}
}
return obj;
}
}
其实我们就在原先的基础上加了两行代码就可以解决这个问题
JsonName jsonName = field.getAnnotation(JsonName.class);
String fieldName = jsonName == null ? field.getName():jsonName.name();
好啦,到这里我们的注解的讲解就结束了,相信大家已经对注解已经有认知了,其实我们用注解可以完成很多"自动化的操作",不仅仅体现在创建建表上,JDBC的增删改查都可以使用注解来完成通用的操作。如果大家想要了解更多关于JDBC的封装,可以戳 Marco’s Java 之【JDBC辅助类封装】
如果有什么不足之处还请大家提出来互相学习~