JDK进阶(5):注解

理解Java注解

声明注解与元注解

声明注解
  1. Java注解就是一种特殊的接口,@interface 关键字声明注解,如自定义注解
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {}
元注解
  1. @Documented:被其修饰的注解会生成到javadoc中。使用javadoc命令生成文档
  2. @Inherited:允许子类继承父类中的注解,但这并不是真的继承,只是使用 @Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解
  3. @Target:用来约束注解可以应用的地方(如方法、类或字段),其中ElementType是枚举类型:
  • 当注解未指定Target值时,则此注解可以用于任何元素之上
  • 多个值使用{}包含并用逗号:@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public enum ElementType {
    /**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
    TYPE,

    /** 标明该注解可以用于字段(域)声明,包括enum实例 */
    FIELD,

    /** 标明该注解可以用于方法声明 */
    METHOD,

    /** 标明该注解可以用于参数声明 */
    PARAMETER,

    /** 标明注解可以用于构造函数声明 */
    CONSTRUCTOR,

    /** 标明注解可以用于局部变量声明 */
    LOCAL_VARIABLE,

    /** 标明注解可以用于注解声明(应用于另一个注解上)*/
    ANNOTATION_TYPE,

    /** 标明注解可以用于包声明 */
    PACKAGE,

    /**
     * 标明注解可以用于类型参数声明(1.8新加入)
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * 用于标注任意类型(不包括class)(1.8新加入)
     * @since 1.8
     */
    TYPE_USE
}

  1. @Retention:用来约束注解的生命周期,分别有三个值:
  • 源码级别 SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)

  • 类文件级别 CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning

  • 运行时级别 RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping

注解元素及其数据类型

  1. 注解元素支持的数据类型(不允许使用任何包装类型)
  • 所有基本类型
  • String
  • Class
  • enum
  • Annotation(嵌套注解)
  • 上述类型的数组
  1. 自定义注解代码:
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {

    /**
     * 字符串类型
     *
     * @return
     */
    String name() default "";

    /**
     * 布尔类型
     *
     * @return
     */
    boolean canShow() default false;

    /**
     * int 类型
     *
     * @return
     */
    int value() default 0;

    /**
     * 数组类型,要么具有默认值,要么在使用注解时传入
     *
     * @return
     */
    String[] names() default "";

    /**
     * 枚举类型
     */
    enum Status {
        /**
         * 修复
         */
        FIXED,
        /**
         * 正常
         */
        NORMAL
    }

    /**
     * class类型
     */
    Class<?> clazz() default Void.class;
}
  1. 编译器对默认值的限制
  2. 注解的元素,要么具有默认值,要么在使用注解时提供元素的值
  3. 对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值

注解的其他属性

注解不支持继承
  1. 注解不支持继承,不能使用关键字extends来继承某个@interface
  2. 注解被编译器编译后,会自动继承 java.lang.annotation.Annotation 接口
// 特殊接口,继承 Annotation
public interface CustomAnnotation extends Annotation{}
快捷方式
  1. 如果注解中定义了名为 value 的元素,且只需要给value赋值,可以省略value=20为直接赋值20
  2. 这限制了元素名必须为value
// 定义注解
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GroupAnnotation {
    int value() default 0;
    String name() default "";
}

// 使用注解
/ 当name也需要赋值时必须采用key=value的方式赋值
@GroupAnnotation(value = 20, name = "Sam")
class AnnotationSuperClass {

}

// 当只想给 value 赋值时,可以使用以下快捷方式,省略value=20
@GroupAnnotation(20)
class AnnotationClass extends AnnotationSuperClass {

}

Java内置注解与其它元注解

  1. @Override:用于标明此方法覆盖了父类的方法,源码如下
  2. @Deprecated:用于标明已经过时的方法或类,源码如下
  3. @SuppressWarning():用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,其实现源码如下
deprecation:使用了不赞成使用的类或方法时的警告;
unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 
fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
path:在类路径、源文件路径等中有不存在的路径时的警告; 
serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告; 
finally:任何 finally 子句不能正常完成时的警告; 
all:关于以上所有情况的警告。

注解与反射机制

  1. 注解都继承了Annotation 接口,Java使用Annotation接口代表注解元素
  2. Java 在反射包下有AnnotatedElement 接口,主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,ConstructorFieldMethodPackageClass类都实现了该接口
  3. AnnotatedElement 中相关的API方法如下:
返回值方法名称说明
getAnnotation(Class annotationClass)该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。
Annotation[]getAnnotations()返回此元素上存在的所有注解,包括从父类继承的
booleanisAnnotationPresent(Class<? extends Annotation> annotationClass)如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
Annotation[]getDeclaredAnnotations()返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组
  1. 简单示例
@CustomAnnotation
@GroupAnnotation(value = 20, name = "Sam")
class AnnotationSuperClass {

}

@GroupAnnotation(20)
class AnnotationClass extends AnnotationSuperClass {

}

static void test() {
    Class<AnnotationClass> clazz = AnnotationClass.class;

    // 根据指定注解类型获取该注解
    GroupAnnotation annotation = clazz.getAnnotation(GroupAnnotation.class);
    System.out.println("GroupAnnotation: " + annotation);

    // 获取该元素上的所有注解,包含从父类继承
    Annotation[] annotations = clazz.getAnnotations();
    System.out.println("getAnnotations: " + Arrays.toString(annotations));

    // 获取该元素上的所有注解,但不包含继承!
    Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
    System.out.println("getDeclaredAnnotations: " + Arrays.toString(declaredAnnotations));

    //判断注解 CustomAnnotation 是否在该元素上
    boolean annotationPresent = clazz.isAnnotationPresent(CustomAnnotation.class);
    System.out.println("isAnnotationPresent: " + annotationPresent);



    /*
    输出:
    GroupAnnotation: @com.learning.optimize.jdk.annotation.GroupAnnotation()
    getAnnotations: [@com.learning.optimize.jdk.annotation.CustomAnnotation(name=, names=[], value=0, showSupport=false), @com.learning.optimize.jdk.annotation.GroupAnnotation()]
    getDeclaredAnnotations: [@com.learning.optimize.jdk.annotation.GroupAnnotation()]
    isAnnotationPresent: true
    */
}
    
输出:
GroupAnnotation: @com.learning.optimize.jdk.annotation.GroupAnnotation(name=, value=20)
getAnnotations: [@com.learning.optimize.jdk.annotation.CustomAnnotation(name=, canShow=false, names=[], clazz=class java.lang.Void, value=0), @com.learning.optimize.jdk.annotation.GroupAnnotation(name=, value=20)]
getDeclaredAnnotations: [@com.learning.optimize.jdk.annotation.GroupAnnotation(name=, value=20)]
isAnnotationPresent: true

注解的简单使用:演示利用运行时注解来组装数据库SQL的构建语句的过程

  1. 注解定义
// 表注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    /**
     * 表的名字
     *
     * @return
     */
    String name();
}

// 列注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {

    /**
     * 对应数据库表的列名
     *
     * @return
     */
    String name() default "";

    /**
     * 列类型分配的长度,如varchar(30)的30
     *
     * @return
     */
    int value() default 16;

    /**
     * 列约束
     *
     * @return
     */
    Constraints constraint() default @Constraints;
}

// 列约束
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    /**
     * 判断是否作为主键约束
     */
    boolean primaryKey() default false;

    /**
     * 判断是否允许为null,默认不为 null
     *
     * @return
     */
    boolean allowNull() default false;

    /**
     * 判断是否唯一,默认不唯一
     *
     * @return
     */
    boolean unique() default false;
}

  1. 注解使用的实体
@Table(name = "sys_person")
@Setter
@Getter
public class Person {
    /**
     * 主键ID
     */
    @Column(name = "ID", value = 50, constraint = @Constraints(primaryKey = true))
    private String id;

    /**
     * 姓名
     */
    @Column(name = "NAME", value = 30)
    private String name;

    /**
     * 年龄
     */
    @Column(name = "AGE")
    private int age;

    /**
     * 个人描述
     */
    @Column(name = "DESCRIPTION", value = 150, constraint = @Constraints(allowNull = true))
    private String description;


}
  1. 组装数据库SQL的构建(表创建)
public class TableCreator {

    public static String createTableSql(String className) throws ClassNotFoundException {
        Class<?> cl = Class.forName(className);
        Table dbTable = cl.getAnnotation(Table.class);
        //如果没有表注解,直接返回
        if (dbTable == null) {
            System.out.println("No DBTable annotations in class " + className);
            return null;
        }
        String tableName = dbTable.name();
        // If the name is empty, use the Class name:
        if (tableName.length() < 1) {
            tableName = cl.getName().toUpperCase();
        }
        List<String> columnDefs = new ArrayList<String>();
        //通过Class类API获取到所有成员字段
        for (Field field : cl.getDeclaredFields()) {
            String columnName;
            String columnType;
            //获取字段上的注解
            Annotation[] anns = field.getDeclaredAnnotations();
            if (anns.length < 1) {
                continue; // Not a db table column
            }

            // 这边简单处理
            Class<?> type = field.getType();
            if (type == int.class || type == Integer.class) {
                columnType = " INT(%s)";
            } else {
                columnType = " VARCHAR(%s)";
            }

            for (Annotation ann : anns) {
                if (ann instanceof Column) {
                    Column column = (Column) ann;

                    if (column.name().length() < 1) {
                        columnName = field.getName().toUpperCase();
                    } else {
                        columnName = column.name();
                    }
                    //构建语句
                    columnDefs.add(columnName + String.format(columnType, column.value()) + getConstraints(column.constraint()));
                }
            }
        }


        //数据库表构建语句
        String columns = Joiner.on(",\n    ").join(columnDefs);
        return "CREATE TABLE " + tableName + "(\n    " + columns + "\n);";
    }


    /**
     * 判断该字段是否有其他约束
     *
     * @param con
     * @return
     */
    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;
    }

    public static void main(String[] args) throws Exception {

        String className = "com.learning.optimize.jdk.annotation.sql.Person";
        System.out.println(createTableSql(className));
    }
}

输出:
CREATE TABLE sys_person(
    ID VARCHAR(50) NOT NULL PRIMARY KEY,
    NAME VARCHAR(30) NOT NULL,
    AGE INT(16) NOT NULL,
    DESCRIPTION VARCHAR(150)
);

Java 8中注解增强

元注解Repeatable

  1. 表示在同一个位置重复相同的注解。
  2. 示例
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPath {
    String [] value();
}

// JDK8 以前这样使用
@FilterPath({"/update","/add"})
public class A { }
// 使用Java8新增 @Repeatable原注解
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class) // 参数指明接收的注解class
public @interface FilterPath {
    String  value();
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
    FilterPath[] value();
}

// Java8 可以这样使用
@FilterPath("/web/update")
@FilterPath("/web/add")
public class A {}

新增的两种 ElementType

static void test2() {

    // 可以用于标注类型参数 class D<@Parameter T> { }
    ElementType typeParameter = ElementType.TYPE_PARAMETER;

    // 用于标注任意类型(不包括class)
    ElementType typeUse = ElementType.TYPE_USE;
}

参考

  1. 源码地址:Java 进阶
  2. java进阶(四):Java注解学习
  3. 深入理解Java注解类型(@Annotation))

Fork me on Gitee

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值