Java之注解(Annotation)浅析
小弟还在新手阶段,所以只能起个“浅析”的名字,不求能带给大家多少进步,最大的作用就是自己总结一下,方便以后回顾,如果有人能从这篇文章中得到一点启示,那再好不过了。
先推荐一篇关于注解的文章,讲的很详细https://blog.csdn.net/briblue/article/details/73824058
1.注解的概念
讲什么之前不都得先讲概念吗,总得知道它是个什么东西。依据文档,注解就是源程序中的元素关联任何信息和任何元数据的途径和方法。听起来真的太官方了,我的理解不如大牛这么深,按照我的理解,应该就是给了我们一种途径去访问源程序的一些元素比如字段,方法,类等。
2.JDK自带的注解
我知道目前我用的比较多的有三个,分别是@Override,@Deprecated,@SuppressWarnings。看其他的博客还有其他两个@SafeVarargs,@FunctionalInterface。
@Override:当前方法覆盖了父类的方法。
@Deprecated:表示方法已过时,使用时会有警告。
@SuppressWarnings:表示关闭一些警告信息。
这个就不举例子了,相信大家肯定都能理解。
3.注解的分类
按运行机制分:
源码注解:只在源码中存在,编译成.class文件就没有了。
编译时注解:注解在源码和.class文件中都存在。
运行时注解:在运行阶段还起作用,甚至会影响运行逻辑。
按注解来源分:
来自JDK的注解
来自第三方的注解(spring等)
自己定义的注解
下面我们主要来看看我们自己怎么定义注解。
4.自定义注解
这是我自己做的笔记,献丑了,下面一一来描述。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String value();
}
元注解:注解的注解。意思就是在定义一个注解的时候,使用的注解。
元注解有4个,分别是@Target,@Retention,@Inherited,@Documented,下面分别来说说。
aaa@qq.com意思就是注解可以使用在哪些地方
- ElementType.ANNOTATION_TYPE :可以给一个注解进行注解
- ElementType.CONSTRUCTOR :可以给构造方法进行注解
- ElementType.FIELD :可以给属性进行注解
- ElementType.LOCAL_VARIABLE: 可以给局部变量进行注解
- ElementType.METHOD: 可以给方法进行注解
- ElementType.PACKAGE: 可以给一个包进行注解
- ElementType.PARAMETER: 可以给一个方法内的参数进行注解
- ElementType.TYPE: 可以给一个类型进行注解,比如类、接口、枚举
aaa@qq.com表示注解的存活时长
- RetentionPolicy.SOURCE :注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS :注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
- RetentionPolicy.RUNTIME: 注解可以保留到程序运行的时候,它会被加载进入到 JVM中,所以在程序运行时可以获取到它们。
aaa@qq.com表示该注解是否可以被继承,添加了该语句表示可以被继承。
aaa@qq.com表示生成JavaDoc时会包含注解
@Description("I am a Class")
public class Person {
@Description("I am a Class Method1")
public String getName() {
return null;
}
@Description("I am a Class Method2")
public void setAge() {
}
}
public class Teacher extends Person {
public String getName() {
return "liu";
}
public void setName() {
}
}
可以看到Teacher继承了Person,在Person类中使用了注解,那么我们用反射看看,Teacher中是否也得到了父类中的注解呢?
public class TestDescription {
public static void main(String[] args) {
try {
// 获取类类型
Class<?> c = Class.forName("com.codeliu.Teacher");
// 判断Son类是否使用了注解
boolean isUse = c.isAnnotationPresent(Description.class);
if(isUse) {
// 获得注解实例
Description a = c.getAnnotation(Description.class);
System.out.println(a.value());
}
// 获取类中的所有方法,不包括继承的,包括私有的
//Method[] m = c.getDeclaredMethods();
// 获取类中的所有方法,包括继承的,不包括私有的
Method[] m = c.getMethods();
for(Method method:m) {
System.out.println(method.getName());
boolean isMUse = method.isAnnotationPresent(Description.class);
if(isMUse) {
Description s = method.getAnnotation(Description.class);
System.out.println(s.value());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出
I am a Class
getName
setName
setAge
I am a Class Method2
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
可以看到,继承了类的注解,还有另一个方法的注解,总结如下:
/**
* @author liu
* @version 创建时间:2018年4月26日 上午9:47:21
* 利用反射获取注解
* 注意:此时注解的@Retention的值一定要是RUNTIME,不然反射无法获取
*
* 父子类继承注解这块分两种情况,一个是注解定义了@Inherited,一个是没定义。
* 在每种情况中又分类上的注解,子类实现父类抽象方法,继承了父类方法,覆盖了父类方法这四种情况,具体继承规则如下:
* 1. 编写自定义注解时未写@Inherited的运行结果:
* 子类的类上能否继承到父类的类上的注解? 否
* 子类方法,实现了父类上的抽象方法,这个方法能否继承到注解? 否
* 子类方法,继承了父类上的方法,这个方法能否继承到注解? 能
* 子类方法,覆盖了父类上的方法,这个方法能否继承到注解? 否
*
* 2. 编写自定义注解时写了@Inherited的运行结果:
* 子类的类上能否继承到父类的类上的注解? 能
* 子类方法,实现了父类上的抽象方法,这个方法能否继承到注解? 否
* 子类方法,继承了父类上的方法,这个方法能否继承到注解? 能
* 子类方法,覆盖了父类上的方法,这个方法能否继承到注解? 否
*/
同时还有一个要注意的是要利用反射获取注解,则@Retention(RetentionPolicy.RUNTIME)的值一定要是RUNTIME,因为反射是在编译后运行时动态的加载类,如果不设置为这个,运行时注解已经gg了。
还有其他一些知识点:
注解中给的成员类型是受限制的,合法的类型是基本数据类型,String、Class、Annotation、Enumeration。
如果注解只有一个成员,则成员名必须为value(),在使用时可以忽略成员名和=。比如上面的@Description(“I am a
Class”)没有成员的注解叫标识注解,仅仅起一个标识的作用。和标识接口差不多。
下面看一个多注解的情况
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String value();
int age() default 22;
String name();
}
使用它
@Description(value = "I am a Class", age = 20, name = "CodeTiger")
5.注解和反射结合应用
第一个案例就copy一下大牛那篇博文的,检测代码有没有bug
我要写一个测试框架,测试程序员的代码有无明显的异常。
—— 程序员 A : 我写了一个类,它的名字叫做 NoBug,因为它所有的方法都没有错误。
—— 我:自信是好事,不过为了防止意外,让我测试一下如何?
—— 程序员 A: 怎么测试?
—— 我:把你写的代码的方法都加上 @Jiecha 这个注解就好了。
—— 程序员 A: 好的。
NoBug.java
package ceshi;
import ceshi.Jiecha;
public class NoBug {
@Jiecha
public void suanShu(){
System.out.println("1234567890");
}
@Jiecha
public void jiafa(){
System.out.println("1+1="+1+1);
}
@Jiecha
public void jiefa(){
System.out.println("1-1="+(1-1));
}
@Jiecha
public void chengfa(){
System.out.println("3 x 5="+ 3*5);
}
@Jiecha
public void chufa(){
System.out.println("6 / 0="+ 6 / 0);
}
public void ziwojieshao(){
System.out.println("我写的程序没有 bug!");
}
}
上面的代码,有些方法上面运用了 @Jiecha 注解。
这个注解是我写的测试软件框架中定义的注解。
package ceshi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Jiecha {
}
然后,我再编写一个测试类 TestTool 就可以测试 NoBug 相应的方法了。
package ceshi;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestTool {
public static void main(String[] args) {
NoBug testobj = new NoBug();
Class clazz = testobj.getClass();
Method[] method = clazz.getDeclaredMethods();
//用来记录测试产生的 log 信息
StringBuilder log = new StringBuilder();
// 记录异常的次数
int errornum = 0;
for ( Method m: method ) {
// 只有被 @Jiecha 标注过的方法才进行测试
if ( m.isAnnotationPresent( Jiecha.class )) {
try {
m.setAccessible(true);
m.invoke(testobj, null);
} catch (Exception e) {
//e.printStackTrace();
errornum++;
log.append(m.getName());
log.append(" ");
log.append("has error:");
log.append("\n\r caused by ");
//记录测试过程中,发生的异常的名称
log.append(e.getCause().getClass().getSimpleName());
log.append("\n\r");
//记录测试过程中,发生的异常的具体信息
log.append(e.getCause().getMessage());
log.append("\n\r");
}
}
}
log.append(clazz.getSimpleName());
log.append(" has ");
log.append(errornum);
log.append(" error.");
// 生成测试报告
System.out.println(log.toString());
}
}
输出
1234567890
1+1=11
1-1=0
3 x 5=15
chufa has error:
caused by ArithmeticException
/ by zero
NoBug has 1 error.
提示 NoBug 类中的 chufa() 这个方法有异常,这个异常名称叫做 ArithmeticException,原因是运算过程中进行了除 0 的操作。
所以,NoBug 这个类有 Bug。
这样,通过注解我完成了我自己的目的,那就是对别人的代码进行测试。
所以,再问我注解什么时候用?我只能告诉你,这取决于你想利用它干什么用。
第二个案例,通过输入一定的条件,生成相应的SQL语句。
package com.codeliu;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author liu
* @version 创建时间:2018年4月26日 上午11:43:01
* 注解,数据库表名
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
上面定义了一个注解@Table,用于和数据库表名关联。
package com.codeliu;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author liu
* @version 创建时间:2018年4月26日 上午11:44:30
* 注解,数据库表的列名
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String value();
}
上面定义了一个注解,表示数据库table的列名。
package com.codeliu;
/**
* @author liu
* @version 创建时间:2018年4月26日 上午11:39:34
* 用户信息类
*/
@Table("user")
public class User {
// 姓名
@Column("name")
private String name;
// 年龄
@Column("age")
private int age;
// 邮箱
@Column("email")
private String email;
// 体重
@Column("weight")
private int weight;
// 电话
@Column("phone")
private String phone;
/**
* @return name
*/
public String getName() {
return name;
}
/**
* @param name 要设置的 name
*/
public void setName(String name) {
this.name = name;
}
/**
* @return age
*/
public int getAge() {
return age;
}
/**
* @param age 要设置的 age
*/
public void setAge(int age) {
this.age = age;
}
/**
* @return email
*/
public String getEmail() {
return email;
}
/**
* @param email 要设置的 email
*/
public void setEmail(String email) {
this.email = email;
}
/**
* @return weight
*/
public int getWeight() {
return weight;
}
/**
* @param weight 要设置的 weight
*/
public void setWeight(int weight) {
this.weight = weight;
}
/**
* @return phone
*/
public String getPhone() {
return phone;
}
/**
* @param phone 要设置的 phone
*/
public void setPhone(String phone) {
this.phone = phone;
}
}
上面是一个用户信息类,我们就脑海里想象一下数据库有一张表user,然后user表里有一些字段。在类上使用了@Table(“user”),在各个属性上使用了@Column。然后我们来看看怎么用反射来获取注解,进而生成相应的sql。
package com.codeliu;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;
/**
* @author liu
* @version 创建时间:2018年4月26日 上午11:45:41
* 根据输入的条件,生成相应的sql语句
*/
public class GenerateSQL {
public static void main(String[] args) {
Scanner read = new Scanner(System.in);
System.out.println("请输入要查询的条件,用空格分割,不填的条件输入null或0,顺序为name,age,email,weight,phone:");
String input = read.nextLine();
String[] inputs = input.split(" ");
User user = new User();
user.setName(inputs[0]);
if(!"null".equals(inputs[1])) {
user.setAge(Integer.parseInt(inputs[1]));
}
user.setEmail(inputs[2]);
if(!"null".equals(inputs[3])) {
user.setWeight(Integer.parseInt(inputs[3]));
}
user.setPhone(inputs[4]);
String sql = createSQL(user);
System.out.println(sql);
read.close();
}
private static String createSQL(Object user) {
StringBuffer sb = new StringBuffer();
try {
// 通过反射获取到相应的类类型
Class<?> c = user.getClass();
// 获取类上的注解
boolean q1 = c.isAnnotationPresent(Table.class);
if(q1) {
sb.append("select * from ");
Table table = c.getAnnotation(Table.class);
// 获取表名
String tableName = table.value();
sb.append(tableName).append(" where 1 = 1 ");
}
// 获取类中的所有字段,使用getDeclaredField才能访问到私有字段
Field[] fields = c.getDeclaredFields();
for(Field f:fields) {
boolean q2 = f.isAnnotationPresent(Column.class);
if(q2) {
Column col = f.getAnnotation(Column.class);
// 获取字段名
String colName = col.value();
// 获取get方法
Method method = c.getMethod("get" + colName.substring(0, 1).toUpperCase() + colName.substring(1));
// 调用方法获取值
Object colValue = method.invoke(user);
// 如果该字段没有设置值,则查看下一个字段
if(colValue == null || colValue.equals("null") || (colValue instanceof Integer && (int)colValue == 0)) {
continue;
}
sb.append("and ").append(colName);
// 如果值是String类型的
if(colValue instanceof String) {
// 如果值是多条件的
if(((String) colValue).contains(",")) {
sb.append(" in ");
sb.append("(");
// 进行分割
String[] values = ((String) colValue).split(",");
for(String v:values) {
sb.append("'" + v + "'" + ",");
}
// 删除最后一个逗号
sb.deleteCharAt(sb.length() - 1);
sb.append(") ");
} else {
sb.append(" = '" + colValue + "' ");
}
} else {
// 如果是数值类型的
sb.append(" = " + colValue + " ");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
sb.append(";");
return sb.toString();
}
}
运行输入如下
请输入要查询的条件,用空格分割,不填的条件输入null或0,顺序为name,age,email,weight,phone:
CodeTiger 18 null null null
select * from user where 1 = 1 and name = 'CodeTiger' and age = 18 ;
这个程序极其简陋,不过简单的条件还是可以hold住的,这个例子最主要是感受一**解和反射的结合和作用,功能是其次。
好了,就说到这里了,希望大家都能共同进步。