完整代码: 代码
前言
先做个简单介绍.
java从1.5开始支持注解,有三个内置注解(1.8以后又增加了几个)
三个内置注解
| 内置注解 | 作用 |
|---|---|
@Override |
表示当前的方法定义覆盖超类的方法. |
@Deprecated |
如果程序中使用了注解为它的元素,编译器会发出警告信息 |
@SuppressWarnings |
关闭不当的编译器警告信息 |
四种元注解
负责新注解的创建,专门用于注解其他的注解的.
| 元注解 | 作用 |
|---|---|
@Target |
表示注解可以用于ElementType什么地方CONSTRUCTOR:构造器的声明FIELD:域声明(包括enum实例)LOCAL_VARIABLE:局部变量声明METHOD:方法声明PACKAGE:包声明PARAMETER:参数声明TYPE:类,接口(包括注解类型)或enum声明 |
@Retention |
表示需要在什么级别保存该注解信息RetentionPolicy.SOURCE:注解将被编译器丢弃RetentionPolicy.CLASS:注解在class文件中可用,会被VM丢弃RetentionPolicy.RUNTIME:VM将在运行期也保留注解,因此可以通过反射机制来获取注解的信息 |
@Documented |
将此注解包含在Javadoc中 |
@Inherited |
允许子类继承父类中的注解 |
例子1:计算缺失的测试用例
接下来将用一个例子来看看注解在程序运行期间是如何工作的,所以这里主要用到的是
RetentionPolicy.RUNTIME,别的特性会另外说明.
下面的例子是要统计一下有哪些
TestCase是没有测试到的
先自定义一个注解
TestCase
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestCase {
int id();
String name() default "no description";
}
然后再写一个
Cases类包含了所有的测试用例
public class Cases {
@TestCase(id=1)
public void test_1() {}
@TestCase(id=2, name="test_2")
public void test_2() {}
@TestCase(id=3, name="test_3")
public void test_3() {}
}
如果我们不解析这个注解,那这个注解跟注释就没有太大区别,接下来就用
java反射机制来解析注解(如果不了解反射机制没关系,这里只利用其中的一些简单方法,后续会有相应博客来说明java反射机制)
定义一个
TestCaseParse类
public class TestCaseParse {
public static void main(String[] args) {
testCaseParse(Cases.class);
}
public static void testCaseParse(Class<?> clazz) {
// 拿到所有的方法(包括private)
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
//两种方式都可以拿到m方法上的TestCase注解实例
//TestCase testCase1 = (TestCase)m.getAnnotations()[0];
//TestCase testCase2 = m.getAnnotation(TestCase.class);
TestCase testCase = m.getAnnotation(TestCase.class);
System.out.println("id=" + testCase.id() + ", name=" + testCase.name());
}
}
}
1: 其中
Class.getDeclaredMethods()方法是可以拿到该Class的所有方法包括private,default,protected,public方法,但是没有包括父类中的方法.
2.其中可以有两种方法拿到对应方法上面的
TestCase实例,至于Annotation和实例之间有什么联系或者区别,以后会进行讨论,目前我们只关心怎么可以拿到这个实例,因为拿到实例就可以拿到相应的属性了.
TestCase testCase1 = (TestCase)m.getAnnotations()[0];
TestCase testCase2 = m.getAnnotation(TestCase.class);
结果输出: 对于
id=1的时候因为没有给name赋值,所以采用的是默认值.
id=3, name=test_3
id=1, name=no description
id=2, name=test_2
计算缺失的测试用例
既然我们已经可以拿到测试方法上面的注解的实例,那统计一下数据就是自热而然的事情了. 在
TestCaseParse类中再加入一个方法.
public class TestCaseParse {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6));
countMissingTestCase(Cases.class, set);
}
public static void countMissingTestCase(Class<?> clazz, Set<Integer> set) {
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
TestCase testCase = m.getAnnotation(TestCase.class);
int id = testCase.id();
if (set.contains(id)) set.remove(id);
}
System.out.println("missing testcase : ");
for (int id : set) System.out.println("id=" + id);
}
}
输出:
missing testcase :
id=4
id=5
id=6
相信到这里对注解应该有了一个稍微直观一点的感受了.
自定义注解的规则
注解元素可用的类型如下:
| 内置注解 | 可用类型 |
|---|---|
| 自定义注解 |
1. 所有基本类型(int,float,boolean等等)2. String3. Class4. enum5. Annotation6. 以上类型的数组 |
默认值限制:
1.元素不能有不确定的值,所以必须要么有默认值,要么在使用注解时提供元素的值
2.对于非基本类型的元素,不能以null作为它的值,所以在定义时的默认值也不能为null.
关于Value()
Note:当注解中有
value值时,如果传入的值没有指定任何元素,默认是给value的
import java.lang.annotation.*;
import java.lang.reflect.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable {
//String name();
String name() default "no name";
//String value() default "no value";
String value();
}
class TestValue {
// 默认给value的值
@Testable("value")
public void test_1() {}
}
class Parse {
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Method m = TestValue.class.getMethod("test_1");
Testable testable = m.getAnnotation(Testable.class);
System.out.println("name=" + testable.name() + ", value=" + testable.value());
}
}
此时按下面这种写法会默认把值给
value,不管Testable中value()是否有默认值,结果都是
name=no name, value=value
Testable |
结果 |
|---|---|
String name();String value();
|
报错,因为没有给name赋值 |
String name() default "no name";String value();
|
name=no name, value=value |
String name() default "no name";String value() default "no value";
|
name=no name, value=value |
String name();String value() default "no value";
|
报错,因为没有给name赋值 |
注解的嵌套
其实没有什么,就是把很多注解的公共部分提取出来,由于没有继承的概念,因此就用了这种嵌套的方式,看完代码你就懂了
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Nest {
boolean display() default true;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface OutNest {
String name() default "no name";
Nest nest() default @Nest;
}
public class TestNest {
public static void main(String[] args) throws NoSuchMethodException, SecurityException{
OutNest outNest = TestNest.class.getMethod("test").getAnnotation(OutNest.class);
System.out.println("name=" + outNest.name() + ", nest=" + outNest.nest());
System.out.println("display=" + outNest.nest().display());
}
@OutNest(name="test", nest=@Nest(display=false))
public static void test() {}
}
输出:
name=test, nest=@com.jianshu.annotaion.Nest(display=false)
display=false
下面的例子中会用到嵌套,会有更深的体会.
例子2:根据实例生成数据库语句
先定义一个共有的注解
Constraints
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primaryKey() default false;
boolean allowNull() default true;
boolean unique() default false;
}
再定义一系列用于标注属性的注解
SQLBoolean,SQLInteger,SQLCharacter,SQLString
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLBoolean {
String name() default "";
Constraints constraints() default @Constraints;
}
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraints() default @Constraints;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLCharacter {
String name() default "";
int value() default 0;
Constraints constraints() default @Constraints;
}
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
String name() default "";
int value() default 0;
Constraints constraints() default @Constraints;
}
再定义一个设置数据表的注解
DBTable
import java.lang.annotation.*;
@Target(ElementType.TYPE) //因为要放到类上
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
String name();
}
定义一下
User类并且在各个成员变量中加入合适的注解
@DBTable(name="user")
public class User {
@SQLString(30) String username;
@SQLString(value=20,
constraints=@Constraints(allowNull=false))
String password;
@SQLInteger int age;
@SQLCharacter(value=15,
constraints=@Constraints(primaryKey=true))
String handle;
@SQLBoolean(name="VIP") boolean isVIP;
}
最后加上用于生成
SQL语句的类CreateSQL
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
public class CreateSQL {
public static void main(String[] args) {
System.out.println(createSQL(User.class));
}
public static String createSQL(Class<?> clazz) {
StringBuffer sbuffer = new StringBuffer();
DBTable table = clazz.getAnnotation(DBTable.class);
if (table == null) return null;
sbuffer.append("create table " + table.name() + "(\n");
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
String columnName = f.getName();
Annotation anno = f.getAnnotations()[0];
if (anno instanceof SQLString) {
sbuffer.append(" " + sqlStr((SQLString)anno, columnName) + "\n");
} else if (anno instanceof SQLInteger) {
sbuffer.append(" " + sqlInt((SQLInteger)anno, columnName) + "\n");
} else if (anno instanceof SQLBoolean) {
sbuffer.append(" " + sqlBool((SQLBoolean)anno, columnName) + "\n");
} else if (anno instanceof SQLCharacter) {
sbuffer.append(" " + sqlCharacter((SQLCharacter)anno, columnName) + "\n");
}
}
return sbuffer.append(");").toString();
}
public static String sqlCharacter(SQLCharacter anno, String columnName) {
if (anno.name().length() > 0) columnName = anno.name();
return columnName + " Character(" + anno.value() + ")" + getConstraints(anno.constraints());
}
public static String sqlBool(SQLBoolean anno, String columnName) {
if (anno.name().length() > 0) columnName = anno.name();
return columnName + " Boolean" + getConstraints(anno.constraints());
}
public static String sqlInt(SQLInteger anno, String columnName) {
if (anno.name().length() > 0) columnName = anno.name();
return columnName + " Integer" + getConstraints(anno.constraints());
}
public static String sqlStr(SQLString anno, String columnName) {
if (anno.name().length() > 0) columnName = anno.name();
return columnName + " VARCHAR(" + anno.value() + ")" + getConstraints(anno.constraints());
}
public static String getConstraints(Constraints cons) {
String str = "";
if (cons.primaryKey()) str += " PRIMARYKEY";
if (!cons.allowNull()) str += " NOT NULL";
if (cons.unique()) str += " UNIQUE";
return str;
}
}
结果输出:
create table user(
username VARCHAR(30)
password VARCHAR(20) NOT NULL
age Integer
handle Character(15) PRIMARYKEY
VIP Boolean
);
总结
今天主要是通过几个例子来了解一下关于注解在运行时期通过反射机制是如何工作的,后续博客还会有关于注解与类之间的关系是如何的,以及在其他的一些时期(比如编译期)是如何工作.
参考:
Java编程思想第四版