第20章 注解

第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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值