Thinking In Java——注解读书笔记及摘录

基本语法

注解为我们在代码中添加信息提供了一种形式化的方式,使我们在稍后某一个时刻非常方便地使用这些数据。

定义注解

定义注解时,会需要一些元注解。

@Target用来定义该注解用于什么地方。

@Retention用来定义该注解在哪一个级别使用,在源代码中(SOURCE)、类文件中(CLASS)或者运行时(RUNTIME)。

在注解中,一般会包含一些元素以表示某些值。当分析处理注解时,程序或工具可以利用这些值。注解的元素看起来就像是接口的方法,唯一的区别是可为其指定默认值。

没有元素的注解成为标记注解。

自定义@UseCase注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
    public int id();
    public String description() default "no description";
}

元注解

内置的标准注解和元注解:

注解说明
@Target表示该注解可以用于什么地方。
可能的ElementType参数包括:
TYPE:类、接口(包括注解类型)或enum声明
TYPE_PARAMETER:类型参数声明(Java 8加入)
TYPE_USE:类型的使用(Java 8加入)
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:包声明
PARAMETER:参数声明
CONSTRUCTOR:构造器的声明
ANNOTATION_TYPE:注解类型声明
MODULE:模块声明(Java 9加入)
@Retention表示需要在什么级别保存该注解信息。
可选的RetentionPolicy参数包括:
SOURCE:注解将被编译器丢弃
CLASS:注解在class文件中可用,但会被VM丢弃
RUNTIME:VM将在运行期间也会保留注解,因此可以通过反射机制读取注解的信息
@Documented将此注解包含在Javadoc中。
@Inherited允许子类继承父类中的注解。

编写注解处理器

编写@UseCase的注解处理器:

public class UserCaseTracker {

    public static void trackUseCases(List<Integer> useCases, Class<?> cl){
        for (Method m : cl.getDeclaredMethods()){
            UseCase uc = m.getAnnotation(UseCase.class);
            if (uc != null){
                System.out.println("Found Use Case:" + uc.id() + " , des:" + uc.description());
                useCases.remove(Integer.valueOf(uc.id()));
                if (m.getName().equals("showMsg")){
                    try {
                        m.invoke(null);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        for (int i : useCases) {
            System.out.println("Warning : Missing use case-" + i);
        }
    }

    public static void main(String[] args) {
        List<Integer> useCases = new ArrayList<>();
        Collections.addAll(useCases, 46, 47, 48, 49, 50);
        trackUseCases(useCases, PasswordUtils.class);
    }
}
public class PasswordUtils {

    @UseCase(id = 46)
    public static void showMsg(){
        System.out.println("show msg!");
    }

    @UseCase(id = 47, description = "Passwords must contain at least one numeric")
    public boolean validatePassword(String password){
        return (password.matches("\\w*\\d\\w*"));
    }

    @UseCase(id = 48)
    public String encryptPassword(String password){
        return new StringBuilder(password).reverse().toString();
    }

    @UseCase(id = 49, description = "New Passwords can't equal previously used ones")
    public boolean checkForNewPassword(List<String> prevPasswords, String password){
        return !prevPasswords.contains(password);
    }

}

输出:

/*
out:
Found Use Case:49 , des:New Passwords can't equal previously used ones
Found Use Case:46 , des:no description
show msg!
Found Use Case:48 , des:no description
Found Use Case:47 , des:Passwords must contain at least one numeric
Warning : Missing use case-50
 */

示例代码使用了两个反射方法:getDeclaredMethods()和getAnnotation(),它们都属于AnnotatedElement接口(Class、Method以及Field都实现了这个接口)。

getAnnotation()方法返回指定类型的注解对象。如果被注解的方法上没有该类型的注解(使用注解的方法上没有该注解),则返回null。然后调用id()和description()方法从返回的UseCase对象中提取元素的值。

注解元素

注解元素可用的类型:

  • 所有基本类型(int、float、boolean、double、char、byte、short、long)
  • String
  • Class
  • enum
  • Annotation
  • 以上类型的数组

如果使用了其它类型,编译器会报错,即使是包装类也是不可以的。

注解可以作为元素的类型,也就是说可以嵌套。

默认值限制

元素不能有不确定的值,也就是说元素必须要么有默认值,要么在使用注解时提供元素的值。

对于非基本类型的元素,无论是在源代码中声明时,或者在注解接口中定义默认值时,都不能以null作为其值。这个约束使得处理器很难表现一个元素的存在或者缺失的状态,因为在每一个注解的声明中,所有的元素都存在,并且具有相应的值。

生成外部文件

假设希望提供一些基本的对象/关系映射功能,能够自动生成数据库表,用以存储JavaBean对象。可以使用XML描述文件,指明类的名字、每个成员以及数据库映射的相关信息。

如果使用注解的话,可以将所有信息都保存在JavaBean源文件中。为此,需要一批新的注解,用于定义与Bean关联的数据库表的名字,以及与Bean属性关联的列的名字和SQL类型。

创建数据库表注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BDTable {
    public String name() default "";
}

可以只指定enum ElementType中的某一个值,或者以逗号分隔的形式指定多个值。如果想将注解应用于所有的ElementType,那么可以省去@Target元注解。

修饰JavaBean域注解:

@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 constrains() default @Constraints;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";
    Constraints constraints() default @Constraints;
}

这些SQL类型具有name()元素和constraints()元素。

后者利用了嵌套注解的功能,将column类型的数据库约束信息嵌入其中。注意constraints()元素的默认值是@Constraints。由于在@Constraints注解类型之后,没有在括号里指明@Constraints中的元素的值,因此注解中的取值均为默认值。

如果需要令unique()元素取值为true:

public @interface Uniqueness {
    Constraints constraints() default @Constraints(unique = true);
}

使用以上注解:

@BDTable(name = "member")
public class Member {

    @SQLString(30)
    private String firstName;

    @SQLString(50)
    private String lastName;

    @SQLInteger
    private Integer age;

    @SQLString(value = 30, constrains = @Constraints(primaryKey = true))
    private String handle;
    
    private static int memberCount;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getHandle() {
        return handle;
    }

    public void setHandle(String handle) {
        this.handle = handle;
    }

    public static int getMemberCount() {
        return memberCount;
    }

    public static void setMemberCount(int memberCount) {
        Member.memberCount = memberCount;
    }

    @Override
    public String toString() {
        return "Member{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", age=" + age +
                ", handle='" + handle + '\'' +
                '}';
    }
}

注解不支持继承

不可以使用关键字extends来继承某个@interface。

实现处理器

实现创建数据库表的处理器:

public class TableCreator {

    private static final String TEST_CLASS_NAME = "AnnotationSystem.CreateProcessor.ExternalFile.Member";

    public static void main(String[] args) throws Exception {
        creator(TEST_CLASS_NAME);
    }

    private static void creator(String className) throws ClassNotFoundException {
        Class<?> cl = Class.forName(className);
        DBTable dbTable = cl.getAnnotation(DBTable.class);
        if (dbTable == null){
            return;
        }
        String tableName = dbTable.name();
        if (tableName.length() < 1){
            tableName = cl.getName().toUpperCase();
        }
        List<String> columnDefs = new ArrayList<>();
        for (Field field :
                cl.getDeclaredFields()) {
            String colName = null;
            Annotation[] anns = field.getAnnotations();
            if (anns.length < 1){
                continue;
            }
            if (anns[0] instanceof SQLInteger){
                SQLInteger sInt = (SQLInteger)anns[0];
                if (sInt.name().length() < 1){
                    colName = field.getName().toUpperCase();
                }else {
                    colName = sInt.name();
                }
                columnDefs.add(colName + " INT" + getConstraints(sInt.constraints()));
            }

            if (anns[0] instanceof SQLString){
                SQLString sString = (SQLString)anns[0];
                if (sString.name().length() < 1){
                    colName = field.getName().toUpperCase();
                }else {
                    colName = sString.name();
                }
                columnDefs.add(colName + " VARCHAR(" + sString.value() + ")");
                getConstraints(sString.constrains());
            }

            StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "(");
            createCommand.append("\n  " + columnDefs + " ,");
            String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");";
            tableCreate = tableCreate.replace("[", "");
            tableCreate = tableCreate.replace("]", "");
            System.out.println("Table Creation SQL for :" + className + " is :\n " + tableCreate);
        }
    }

    private static String getConstraints(Constraints con){
        String constraints = "";
        if (!con.allowNull()){
            constraints += " NOT NULL";
        }
        if (con.primaryKey()){
            constraints += " PRIMARY KEY";
        }
        if (con.unique()){
            constraints += " UNIQUE";
        }
        return constraints;
    }

}

输出:

Table Creation SQL for :AnnotationSystem.CreateProcessor.ExternalFile.Member is :
 CREATE TABLE member(
  FIRSTNAME VARCHAR(30) );
Table Creation SQL for :AnnotationSystem.CreateProcessor.ExternalFile.Member is :
 CREATE TABLE member(
  FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50) );
Table Creation SQL for :AnnotationSystem.CreateProcessor.ExternalFile.Member is :
 CREATE TABLE member(
  FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50), AGE INT NOT NULL );
Table Creation SQL for :AnnotationSystem.CreateProcessor.ExternalFile.Member is :
 CREATE TABLE member(
  FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50), AGE INT NOT NULL, HANDLE VARCHAR(30) );

在示例中的@Constraints注解被传递给getConstraints()方法,由它负责构造一个包含SQL约束条件的String对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值