面试官:什么是 Java 注解?(下)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 面试官:什么是 Java 注解?

02 Java 注解的分类


上面介绍注解的语法和使用,我们遇到了 @Target、@Retention 等没见过的注解,你可能有点懵。但没关系,听我说道说道。Java 中有 @Override、@Deprecated@SuppressWarnings内置注解;也有 @Target、@Retention、@Documented、@Inherited修饰注解的注解,称之为元注解


2.1 内置注解


Java 定义了一套自己的注解,其中作用在代码上的是:


  • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}


  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}


  • @SuppressWarnings - 用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。


@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}


JDK7 之后又加了 3 个,这几个的用法,我也用得很少。就不过多介绍了,感兴趣的小伙伴自行百度分别是:


  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}


  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}


  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}


2.2 元注解


元注解就是修饰注解的注解,分别有:


2.2.1 @Target


用来指定注解的作用域(如方法、类或字段),其中 ElementType 是枚举类型,其定义如下,也代表可能的取值范围


public enum ElementType {
    /**标明该注解可以作用于类、接口(包括注解类型)或enum声明*/
    TYPE,
    /** 标明该注解可以作用于字段(域)声明,包括enum实例 */
    FIELD,
    /** 标明该注解可以作用于方法声明 */
    METHOD,
    /** 标明该注解可以作用于参数声明 */
    PARAMETER,
    /** 标明注解可以作用于构造函数声明 */
    CONSTRUCTOR,
    /** 标明注解可以作用于局部变量声明 */
    LOCAL_VARIABLE,
    /** 标明注解可以作用于注解声明(应用于另一个注解上)*/
    ANNOTATION_TYPE,
    /** 标明注解可以作用于包声明 */
    PACKAGE,
    /**
     * 标明注解可以作用于类型参数声明(1.8新加入)
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * 类型使用声明(1.8新加入)
     * @since 1.8
     */
    TYPE_USE
}


PS:如果 @Target 无指定作用域,则默认可以作用于任何元素上。等同于:


@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})


2.2.2 @Retention


用来指定注解的生命周期,它有三个值,对应 RetentionPolicy 中的三个枚举值,分别是:源码级别(source),类文件级别(class)或者运行时级别(runtime)


  • SOURCE:只在源码中可用


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


  • RUNTIME:在源码,class,运行时均可用,因此可以通过反射机制读取注解的信息(源码、class 文件和执行的时候都有注解的信息),如 SpringMvc 中的 @Controller、@Autowired、@RequestMapping 等。此外,我们自定义的注解也大多在这个级别。


2.2.2.1 理解 @Retention


这里引申一下话题,要想理解 @Retention 就要理解下从 java 文件到 class 文件再到 class 被 jvm 加载的过程了。下图描述了从 .java 文件到编译为 class 文件的过程:


640.png


其中有一个注解抽象语法树的环节,这个环节其实就是去解析注解然后做相应的处理。


所以重点来了,如果你要在编译期根据注解做一些处理,你就需要继承 Java 的抽象注解处理器 AbstractProcessor,并重写其中的 process () 方法。


一般来说只要是注解的 @Target 范围是 SOURCE 或 CLASS,我们就要继承它;因为这两个生命周期级别的注解等加载到 JVM 后,就会被抹除了


比如,lombok 就用 AnnotationProcessor 继承了 AbstractProcessor,以实现编译期的处理。这也是为什么我们使用 @Data 就能实现 get、set 方法的原因。


640.png


2.2.3 @Documented


执行 javadoc 的时候,标记这些注解是否包含在生成的用户文档中。


2.2.4 @Inherited


标记这个注解具有继承性,比如 A 类被注解 @Table 标记,而 @Table 注解被 @Inherited 声明(具备继承性);继承于 A 的子类,也继承 @Table 注解。


//声明 Table 注解,有继承性
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
}


03 自定义注解


好啦,说了这么多理论。大家也听累了,我也聊累了。那怎么自定义一个注解并让它起作用呢?下面我将带着你们看看我司的防止重复提交的注解是怎么实现的?当然,由于设计内部的东西,我只会写写伪代码。思路在前面介绍过了,为方便阅读我拿下来,大家理解就行。


640.png


需求是:同一用户,三秒内重复提交一样的参数,就会报异常阻止重复提交,否则正常提交处理写请求


3.1 定义注解


首先,定义注解必须是 @interface 修饰;其次,有四个考虑的点:


  • 注解的生命周期 @Retention,一般都是 RUNTIME 运行时。
  • 注解的作用域 @Target,作用于写请求,也就是 controller 方法上。
  • 是否需要元素,用分布式锁实现,必须要有锁的过期时间。给定默认值,也支持自定义。
  • 是否生成 javadoc @Documented,这个注解无脑加就对了。


基于此,我司的防止重复提交的自定义注解就出来了:


@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BanReSubmitLock {
    /**
     * 锁定时间,默认单位(秒)默认时间(3秒)
     */
    long lockTime() default 3L;
}


3.2 AOP 切面处理


@Aspect
@Component
public class BanRepeatSubmitAop {
 @Autowired
    private final RedisUtils redisUtils;
    @Pointcut("@annotation(com.nasus.framework.web.annotation.BanReSubmitLock)")
    private void banReSubmitLockAop() {
    }
    @Around("banReSubmitLockAop()")
    public Object aroundApi(ProceedingJoinPoint point) throws Throwable {
  // 获取 AOP 切面方法签名 
        MethodSignature signature = (MethodSignature) point.getSignature();
  // 方法
        Method method = signature.getMethod();
  // 获取目标方法上的 BanRepeatSubmitLock 注解
        BanReSubmitLock banReSubmitLock = method.getAnnotation(BanReSubmitLock.class);
  // 根据用户信息以及提交参数,创建 Redis 分布式锁的 key
        String lockKey = createReSumbitLockKey(point, method);
        // 根据 key 获取分布式锁对象
  Lock lock = redisUtils.getReSumbitLock(lockKey);
  // 上锁
  boolean result = lock.tryLock();
  // 上锁失败,抛异常
        if (!result) {
            throw new Exception("请不要重复请求");
        }
  // 其他处理
  ...
    }
 /**
     * 生成 key
     */
 private String createReSumbitLockKey(ProceedingJoinPoint point, Method method) {
  // 拼接用户信息 & 请求参数
  ...
  // MD5 处理
  ...
  // 返回
 }
}


可以看到这里利用了 AOP 切面的方式获取被 @NoReSubmitLock 修饰的方法,并借此拿到切点(被注解修饰方法)的参数、用户信息等等,通过 MD5 处理,最终尝试上锁。


3.3 使用


public class TestController {
    // NoReSubmitLock 注解修饰 save 方法,防止重复提交
    @NoReSubmitLock
    public boolean save(Object o){
        // 保存逻辑
    }
}


使用也非常简单,只需要一个注解就可以完成大部分的逻辑;如果不用注解,每个写接口的方法都要写一遍防止重复提交的逻辑的话,代码非常繁琐,难以维护。通过这个例子相信你也看到了,注解的作用。


04 总结


本文介绍了注解的作用主要是标记、检查以及解耦;介绍了注解的语法;介绍了注解的元素以及传值方式;介绍了 Java 的内置注解和元注解,最后通过我司的一个实际例子,介绍了注解是如何起作用的?


注解是代码的特殊标记,可以在程序编译、类加载、运行时被读取并做相关处理。其对应 RetentionPolicy 中的三个枚举,其中 SOURCE、CLASS 需要继承 AbstractProcessor (注解抽象处理器),并实现 process () 方法来处理我们自定义的注解。而 RUNTIME 级别是我们常用的级别,结合 Java 的反射机制,可以在很多场景优化代码。



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
16天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
51 7
|
2月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
98 43
Java学习十六—掌握注解:让编程更简单
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
21天前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
59 5
|
1月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
58 4
|
1月前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
57 14
|
1月前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
36 12
|
2月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
1月前
|
Java 编译器
Java进阶之标准注解
Java进阶之标准注解
32 0
下一篇
DataWorks