Java注解详解

今天代码review,发现很多冗余的代码,所以领导让在每个接口上面增加一个注解 标注这个接口的版本号,如果当前项目的版本号小于接口的版本号 接口就弃用 如果想让接口弃用 就把接口的版本号设置为Integer.MAX_VALUE。在这里我复习了一下注解的相关知识。

Java注解是什么

代码中特殊的标记,这些标记可以在编译,类加载和运行中读取,并执行相应的处理。

注解的分类

  • Java自带的标准注解,包括@Override@Deprecated@SuppressWarnings,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。
  • 元注解,元注解是用于定义注解的注解,包括@Retention@Target@Inherited@Documented@Retention用于标明注解被保留的阶段,@Target用于标明注解使用的范围,@Inherited用于标明注解可继承,@Documented用于标明是否生成javadoc文档。
  • 自定义注解,可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解

Java 1.5开始自带的标准注解,包括@Override@Deprecated@SuppressWarnings

@Override表示当前的方法定义将覆盖父类中的方法

@Deprecated表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告

@SuppressWarnings表示关闭编译器警告信息

Java元注解

标记在注解上的注解,对注解进行描述和约束。

  • @Target

@Target(ElementType.ANNOTATION_TYPE),@Target(value={CONSTRUCTOR, FIELDE})

标记位该注解用在什么地方

ElementType枚举定义如下: 

public enum ElementType {
 
    TYPE, // 类、接口、枚举类
 
    FIELD, // 成员变量(包括:枚举常量)
 
    METHOD, // 成员方法
 
    PARAMETER, // 方法参数
 
    CONSTRUCTOR, // 构造方法
 
    LOCAL_VARIABLE, // 局部变量
 
    ANNOTATION_TYPE, // 注解类
 
    PACKAGE, // 可用于修饰:包
 
    TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
 
    TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
 
}
  • @Retention

注解的生命周期

public enum RetentionPolicy {
    SOURCE,    // 源文件保留
    CLASS,       // 编译期保留,默认值
    RUNTIME   // 运行期保留,可通过反射去获取注解信息
}

一般我们用到的注解位RUNTIME,即在运行时通过反射机制读取注解的信息

而像SOURCE,CLass 这种在加载到JVM的时候就会被抹除,就需要在编译期间处理相关的逻辑了。

  • @Documented

jdk1.5引入。被修饰的注解会生成到javadoc中,可以使用javadoc命令生成文档。

  • @Inherited

jdk1.5引入。可以让注解被继承,但这并不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解

  • @Repeatable

允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”

例子: 

//JAVA8 之前
public @interface Roles {
    Role[] roles();
}

public @interface Role {
    String roleName();
}

public class RoleTest {
    @Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")})
    public String doString(){
        return "这是C语言中国网Java教程";
    }
}
//JAVA8之后
public @interface Roles {
    Role[] value();
}

@Repeatable(Roles.class)
public @interface Role {
    String roleName();
}

public class RoleTest {
    @Role(roleName = "role1")
    @Role(roleName = "role2")
    public String doString(){
        return "这是C语言中文网Java教程";
    }
}

Java注解原理(就是一个接口)

注解@interface 是一个实现了Annotation接口的 接口, 然后在调用getDeclaredAnnotations()方法的时候,返回一个代理$Proxy对象,这个是使用jdk动态代理创建,使用Proxy的newProxyInstance方法,传入接口 和InvocationHandler的一个实例(也就是 AnotationInvocationHandler ) ,最后返回

Lombok注解编译期间的处理原理

首先要知道java文件到class 在到class加载到JVM这个过程。

首先JAVA文件会被编译成Class 文件,然后在程序运行的时候会被JVM 的类加载子系统加载Class文件成为Class类。

这个时候第一个了解的是注解的生命周期@Retention,一般我用的是Runtime,在java加载到JVM虚拟机中还会被保留,我们就可以通过反射来获得注解的信息,而Class的生命周期是在java文件编译成class字节码文件的时候还在,在加载到JVM虚拟机中会被消除。Source是在加载到class字节码文件就被消除了。这种就是我们常见的,lombok注解的编译时的增强,会把@data标注的类 编译时增加get/set方法。

这个时候我们需要在编译期间如何增强。Javac在编译的时候会经过如下的过程

词法分析=语法分析=语义分析-注解抽象语法树=字节码生成。

所以重点来了,如果要在编译期间处理注解相关的逻辑,就需要继承AbstractProcesser并实现process方法。

自定义一个编译时增强的注解处理器

LOMBOK缺陷和原理分析

当使用@Data注解时,则有了@EqualsAndHashCode注解,那么就会在此类中存在equals(Object other) 和 hashCode()方法,且不会使用父类的属性,这就导致了可能的问题。

问题:比如,有多个类有相同的部分属性,把它们定义到父类中,恰好id(数据库主键)也在父类中,那么就会存在部分对象在比较时,它们并不相等,却因为lombok自动生成的equals(Object other) 和 hashCode()方法判定为相等,从而导致出错。

就是@data生成的equalsandashacode时不包含父类的

解决方案:

  (1)使用@Getter @Setter @ToString代替@Data并且自定义equals(Object other) 和 hashCode()方法,比如有些类只需要判断主键id是否相等即足矣。

  (2)或者使用在使用@Data时同时加上@EqualsAndHashCode(callSuper=true)注解

Lombok底层原理

Lombok处理流程:

    (1)首先JAVAC解析成抽象语法树(AST);

    (2)然后Lombok根据自己的注解处理器,动态的修改AST,增加新的节点代码;

    (3)最后通过分析和生成字节码。

  自从Java 6起,javac就支持“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。

IDE插件Lombok问题:

    举个栗子:

      现在有一个A类,其中有一些字段,没有创建它们的setter和getter方法,使用了lombok的@Data注解,另外有一个B类,它调用了A类实例的相应字段的setter和getter方法

      编译A类和B类所在的项目,并不会报错,因为最终生成的A类字节码文件中存在相应字段的setter和getter方法。

    但是,IDE有自己的想法,IDE发现B类源代码中所使用的A类实例的setter和getter方法在A类源代码中找不到定义,IDE会认为这是错误。

    所以我们为了让IDE不认为这是错误,让IDE放心,或者说是糊弄IDE都要下载安装Lombok Plugin插件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值