第20章 注解
注解: 也称为元数据,为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻方便的使用这些数据。
内置的三种注解:
@Override:重写方法
@Deprecated:过时的
@SuppressWarnings:关闭警告
20.1 基本语法
定义注解,类似于接口
@Target(ElementType.METHOD) // 可以指定多个范围
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
int id();
String description() default "no";
}
// 没有元素的注解被称为标记注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{}
使用注解
public class MyService {
@UseCase(id = 1,description = "f method")
public static void f(){}
// id必须赋值
@UseCase(id = 2)
public void b(){}
}
元注解
注解 | 值 |
---|---|
**@Target ** | |
** ** | 注解范围: |
CONSTRUCTOR:用于描述构造器
FIELD:用于描述域即类成员变量
LOCAL_VARIABLE:用于描述局部变量
METHOD:用于描述方法
PACKAGE:用于描述包
PARAMETER:用于描述参数
TYPE:用于描述类、接口(包括注解类型) 或enum声明
TYPE_PARAMETER:1.8版本开始,描述类、接口或enum参数的声明
TYPE_USE:1.8版本开始,描述一种类、接口或enum的使用声明 |
| **@Retention **** ** | SOURCE:在源文件中有效(即源文件保留)
CLASS:在class文件中有效(即class保留)
RUNTIME:在运行时有效(即运行时保留) |
| **@Documented **** ** | 将次注解包含在Javadoc中 |
| **@Inherited **** ** | 允许子类继承父类中的注解 |
20.2 编写注解处理器
简单处理器
class Test {
public static void main(String[] args) {
Class c = MyService.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
UseCase annotation = method.getAnnotation(UseCase.class);
if (annotation != null) {
System.out.println(annotation.id());
}
}
}
}
注解元素的类型
基本类型/String/Class/enum/Annotation/以上类型数组
从以上类型可以看到,注解是可以嵌套的。
默认值限制
默认值必须是确定的值,非基本类型不能为空。
// 定义特殊值表示为空
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Def {
int age() default -1;
String name() default "";
}
生成数据库表
// 表注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbTables {
String name() default "";
}
// 字段注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primaryKey() default false;
boolean allowNull() default false;
boolean unique() default false;
}
// 定义字段类型
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
int value() default 0;
String name() default "";
// 嵌套注解
Constraints constraints() default @Constraints;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraints() default @Constraints;
}
@DbTables(name = "MEMBER")
public class Member {
// 当仅向value赋值时可以简写
@SQLString(30)
private String firstName;
@SQLString(50)
private String lastName;
@SQLInteger
private Integer age;
// handle是主键,需要对constraints重写赋值
@SQLString(value = 30,constraints = @Constraints(primaryKey = true))
private String handle;
/*
可以看到上面的写法,有些复杂,当有多种类型时,需要定义多种SQL类型注解,
我们可以定义一个TableColumn,包含各种类型的枚举,但会降低灵活性。
如果不使用枚举,而使用String来描述类型,会有灵活性,但会与具体的数据库绑定。
另外一种方式是使用多种注解,但会导致代码更复杂
*/
/*
注意:注解不支持继承
*/
}
// 处理器
public class TableCreator {
public static void main(String[] args) {
run(Member.class);
}
public static void run(Class<?> c) {
DbTables tables = c.getAnnotation(DbTables.class);
// 获取表明
String tableName = tables.name();
Field[] fields = c.getDeclaredFields();
List<String> stringList = new ArrayList<>();
// 遍历表对象的所有属性
for (Field field : fields) {
Annotation[] annotations = field.getAnnotations();
// 遍历属性的所有注解
for (Annotation annotation : annotations) {
// 组装INT或VARCHAR字段
if (annotation instanceof SQLInteger) {
SQLInteger sqlInteger = ((SQLInteger) annotation);
stringList.add(field.getName() + " INT" + dealConstraints(sqlInteger.constraints()));
} else if (annotation instanceof SQLString) {
SQLString sqlString = (SQLString) annotation;
stringList.add(field.getName() + " VARCHAR(" + sqlString.value() + ")" + dealConstraints(sqlString.constraints()));
}
}
}
StringBuilder sqlStr = new StringBuilder("CREATE TABLE " + tableName + "(" + "\r\n");
// 组装SQL语句
for (String s : stringList) {
sqlStr.append(s).append(",").append("\r\n");
}
sqlStr.deleteCharAt(sqlStr.lastIndexOf(",")).append(")");
System.out.println(sqlStr.toString());
}
// 处理字段的约束条件
public static String dealConstraints(Constraints constraints) {
StringBuilder stringBuilder = new StringBuilder();
if (constraints.primaryKey()) {
stringBuilder.append(" PRIMARY KEY");
}
if (!constraints.allowNull()) {
stringBuilder.append(" NOT NULL");
}
if (constraints.unique()) {
stringBuilder.append(" UNIQUE");
}
return stringBuilder.toString();
}
}
20.3 使用apt处理注解
本节和下节主要讲述了apt的运用,该技术用来生成源文件,目前应用不多。
20.5 基于注解的单元测试
单元测试是对类中的每个方法提供一个或多个测试的一种实践,其目的是为了有规律的测试一个类的各个部分是否具备正确的行为。
public class STest {
// 使用断言
@Test
public void test1(){
assert 1 ==1;
}
// 通过返回值判断测试是否成功
@Test
public boolean test2(){
return 1 > 2;
}
// 通过反射调用私有方法
@Test
public void test3() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
MyService myService = new MyService();
Method f = myService.getClass().getDeclaredMethod("f");
f.setAccessible(true);
Object invoke = f.invoke(myService);
System.out.println(invoke);
}
}