Marco's Java 之【Annotation注解】

前言

在之前的文章 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辅助类封装】
如果有什么不足之处还请大家提出来互相学习~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值