注解基本概念
我们在开发当中经常看到一些注解,例如override,Deprected等,这些注解再常见不过了,但是这些注解到底有什么作用呢?在spring 框架中大量的使用注解,那么它的工作原理又是什么呢?接下来我们来分析一下把。
概念
- 注解即元数据,就是源代码的元数据
- 注解在代码中添加信息提供了一种形式化的方法,可以在后续中更方便的 使用这些数据
- Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。
作用
- 生成文档
- 跟踪代码依赖性,实现替代配置文件功能,减少配置。如Spring中的一些注解
- 在编译时进行格式检查,如@Override等
意义
- 注解之前,XML被广泛的应用于描述元数据,XML的维护越来越糟糕
在需要紧耦合的地方,比XML该容易维护,阅读更方便- 在需要比较多参数设置时,使用xml更方便,而在将某个方法声明为服务时这种紧耦合的情况下,比较适合注解
- XML是松耦合的,注解是紧耦合的
- 对于XML和注解的使用,要具体问题具体分析
- Java的annotation没有行为,只能有数据,实际上就是一组键值对而已。通过解析(parse)Class文件就能把一个annotation需要的键值对都找出来
分类
按照运行机制来分类
1.源码注解
只在源码中出现,编译成class文件就不存在了
2.编译时注解
注解在源码和编译中都存在 例如:Override,Deprected,SuppressWarnings
3.运行时注解
运行阶段起作用,甚至会影响运行逻辑的注解 例如spring框架中的@Autowired
按照来源来分类
- 来自Jdk的注解
- 来自第三方的注解
- 我们自己定义的注解
JDK注解
- Override: 保证编译时 要重写方法的正确性
- Deprected: 提示该方法已经过时
- SuppressWarnings: 关闭特定警告信息
Java元注解
java中元注解有四个: @Retention @Target @Document @Inherited;它负责注解其他注解
@Retention:注解的保留位置
- @Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
- @Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
- @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target: 注解的作用目标
- @Target(ElementType.TYPE) //接口、类、枚举、注解
- @Target(ElementType.FIELD) //字段、枚举的常量
- @Target(ElementType.METHOD) //方法
- @Target(ElementType.PARAMETER) //方法参数
- @Target(ElementType.CONSTRUCTOR) //构造函数
- @Target(ElementType.LOCAL_VARIABLE)//局部变量
- @Target(ElementType.ANNOTATION_TYPE)//注解
- @Target(ElementType.PACKAGE) ///包
@Document:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解
自定义注解
概念就说这么多了,看多了没实践会有点懵,注解到底有什么用啊?注解是怎么使用的啊?接下来我们带着这一系列的疑问来做一个简单的例子。来梳理一下我们疑问
需求
我们模仿一下Hibernate的注解。只通过注解来简单实现它的核心功能
我们在数据库有一张user表。用于存储用户信息,然后我们现在对这张表进行查询功能。使用注解的方式来打印出不同条件来查询表的SQL语句。
准备
我们先准备好数据库中的表对应的bean对象
@Table("user")//自定义注解表名
public class UserBean {
@Column("id")//自定义注解属性名
private int id;
@Column("name")
private String name;
@Column("age")
private int age;
@Column("sex")
private String sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
第一步:定义自己的注解
我们使用元注解对这个自己的注解来定义
表名对于的注解@Table
@Target(ElementType.TYPE)//作用在接口、类、枚举、注解
@Retention(RetentionPolicy.RUNTIME)//运行时的注解
public @interface Table {
String value();
}
字段名对应的注解@Column
@Target(ElementType.FIELD)//字段、枚举的常量
@Retention(RetentionPolicy.RUNTIME)//运行时的注解
public @interface Column {
String value();
}
第二步:获取解析注解
- 使用注解的过程,重要的是创建注解处理器
- Java SE5 扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器
- 注解处理器类库: java.lang.reflect.AnnotatedElement
- 获取对象的class
- 判断这个对象是否有表名的注解
- 获取表名注解的值
- 获取对象的所有属性
- 遍历所有属性判断是否有字段名的注解
- 获取各个字段名的注解的属性值
- 拼接sql语句
public static String querry(Object o) {
StringBuffer sb = new StringBuffer();
//获取这个对象的class
Class oClass = o.getClass();
//判断这个对象中有没有Table注解
boolean isAnnotation = oClass.isAnnotationPresent(Table.class);
if (!isAnnotation) {
throw new NullPointerException("Annotation not found");
}
//获取Table注解
Table table = (Table) oClass.getAnnotation(Table.class);
//获取Table注解的值
String tableName = table.value();
//组装sql语句
sb.append("select * from ").append(tableName).append(" where ");
//获取对象中的所有属性
Field[] fields = oClass.getDeclaredFields();
//逐个遍历属性
for (Field field : fields) {
//判断这个属性中是否有Column这个注解
if (!field.isAnnotationPresent(Column.class)) {
continue;
}
//获取Column注解
Column column = field.getAnnotation(Column.class);
//获取去Column中的值
String name = column.value();
Object value = "";
try {
//获取对应属性的值
Method method = oClass.getMethod("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1));
value = method.invoke(o);
} catch (Exception e) {
e.printStackTrace();
}
//如果属性值为空或者为0
if (value == null || (value instanceof Integer && (Integer) value == 0)) {
continue;
}
//如果为string类型拼接’
if (value instanceof String) {
sb.append(name + "=").append("'").append(value).append("'").append(",");
} else if (value instanceof Integer) {
sb.append(name + "=").append(value).append(",");
}
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
第三步 运行测试
测试代码
public static void main(String[] args) {
UserBean userBean = new UserBean();
userBean.setSex("男");
userBean.setAge(20);
String sql1 = querry(userBean);
System.out.println(sql1);
UserBean userBean2 = new UserBean();
userBean2.setSex("男");
userBean2.setName("张三");
String sql2 = querry(userBean2);
System.out.println(sql2);
}
运行结果
- select * from user where age=20,sex='男'
- select * from user where name='张三',sex='男'
好了到了这里看来我们的自定义注解已经成功了。我们通过自己定义了两个注解Table和Column分别表示表明和属性名。然后再通过反射拿到对应的注解值和属性值,最后拼接成一条Sql。
注解的优缺点及与XML的比较
优:
- 方便,简洁,配置信息和 Java 代码放在一起,有助于增强程序的内聚性
- 若要对配置项进行修改,不得不修改 Java 文件,重新编译打包应用
缺:
- 分散到各个class文件中,维护性较差
- 配置项编码在 Java 文件中,可扩展性差
与XML比较:
- 简洁
- 没有XML配置更强大
- 不便于修改,不便于统一管理