警惕!自定义注解使用不当的排查实录

京东云开发者
• 阅读 147

一、引言

大家好,在日常开发过程中,Java 注解(Annotation)是开发中经常使用的一个手段,用于给代码添加元数据的标记。它们可以提供代码额外的信息,这些信息可以在编译时或运行时被访问。注解不会改变代码的执行逻辑,但可以被编译器、JVM 或框架等工具用于生成额外的代码、提供警告或执行其他操作。注解虽然简单,但在平时开发过程中也会遇到各种各样的问题,本人有幸也遇到过,在此与大家分享一次遇到的注解相关问题,如有错误,还请各位大佬们指正。



二、排查过程

在一次分析接口性能时候,发现老业务代码中以下方法是有添加缓存注解,但是并没有起到缓存的作用,在缓存到期之前仍然去请求了下游JSF接口获取数据,业务代码如下:



警惕!自定义注解使用不当的排查实录



可以看到,该方法getRiskInfoByPerformanceAccount,是根据入参做了内存及Redis缓存的功能。但是在查看接口pfinder调用链路时,发现同一个入参仍然重复了9次去调用下游JSF接口查询。



警惕!自定义注解使用不当的排查实录



既然加了缓存注解,为什么会失效呢,下面开始分析一下具体缓存的代码逻辑:

下面代码是该缓存功能的拦截器实现:



警惕!自定义注解使用不当的排查实录



首先,会把所有的缓存实现放入到实现链中,缓存实现即是去对应的位置(redis、内存、接口等)查询数据;



警惕!自定义注解使用不当的排查实录



然后,再把无缓存的查询实现链加入到列表最后。这个流程正常看也是没有问题的。



通过观察发下,这个接口会有返回null的情况,



警惕!自定义注解使用不当的排查实录



那会不会可能value是null导致了缓存不上呢,继续分析缓存实现逻辑,可以看到缓存实现链执行:



警惕!自定义注解使用不当的排查实录



咱们开始一条一条链路看:

内存缓存实现,可以看到直接跳到下一个(redis)实现链:



警惕!自定义注解使用不当的排查实录



那咱们继续看redis实现链,存储到redis里面是一个对象包含过期时间、缓存值、key等,



警惕!自定义注解使用不当的排查实录



先打了个hitRedisCache标记false,然后通过getValueFromRedisAndConvert方法去查(该方法也并没有对查出来空值的处理),查到且没过期则返回;没查到继续去进入下一个实现链(查询接口:获取具体缓存值的调用RPC或数据库等)。再看写入redis的逻辑:



警惕!自定义注解使用不当的排查实录



调查询接口接口获取到的value也并没有空等判断,直接设置缓存对象里放入redis里。



到此,代码上分析整个流程没发现有什么问题,随后开展了本地调试,调用了两次带cache注解的方法,理论上第二次不会去调查询接口了,通过本地调试也的确没有调用查询接口。那就比较奇怪了,为什么本地测试没问题,测试、预发及线上环境都有这个问题。头大了,想了想还是去测试环境试试,然后在切面类中加上日志去测试环境进行测试,发现测试环境并没有打印日志,说明没进入到切面实现类里面,奇怪了,明明加了注解,为什么没进到切面呢,问题肯定还是注解上的问题,回去继续看代码吧:



警惕!自定义注解使用不当的排查实录



发现加cache注解的方法只有本类中的其他方法调用,并没有其他类调用,至此,问题就比较清晰了。



三、解决思路

上述问题是通过普通的方法调用方式调用目标方法,切面是不会生效的,因为切面主要应用于通过 Spring AOP 或其他代理机制进行的方法调用。在同一个类中的方法调用不会经过代理,因此切面也不会被触发。可以考虑将目标方法提取到一个单独的类中,并通过依赖注入的方式调用目标方法,以确保切面能够生效。

经过修改后,已经可以成功缓存结果,日志验证如下:



警惕!自定义注解使用不当的排查实录





四、总结分析

Java 注解固然可以为我们提供方便,但是需要注意使用场合,不是来个场景就使用注解。下面是一些具体的使用注意事项,供大家参考:

避免循环依赖:不要在注解中使用可能导致循环依赖的类或接口。

不要过度使用注解:注解可以提高代码的可读性和维护性,但过度使用会导致代码复杂。注解适合用于描述简单的元数据信息,对于复杂的业务逻辑,过度使用注解会导致代码难以理解和维护。如果需要在运行时动态修改逻辑,注解并不适合,因为它们在编译时就已经确定了。如果需要根据复杂的条件进行逻辑判断,这种情况下使用注解可能会使代码难以阅读和理解也可能造成频繁修改不利于代码维护。

反射使用:如果需要在运行时通过反射读取注解,确保注解的保留策略至少是RetentionPolicy.RUNTIME。

重写注解:当重写父类方法时,如果父类方法上有注解,需要考虑是否需要在子类方法上也添加相同的注解。

注解继承:注解不会被子类自动继承,如果需要在子类中使用,必须显式添加。

避免重复注解:Java 8 引入了重复注解的概念,但在之前版本中,不能在同一个元素上多次使用同一个注解。

类型检查:在使用注解时,确保类型检查正确,例如,不要将int类型的属性值赋予string类型的注解属性。

性能考虑:运行时注解处理可能会影响性能,尤其是在大量使用反射的情况下。

遵循上面这些使用注释事项,可以帮助大家更有效地使用Java注解,同时保持代码的清晰和可维护性。

作者:京东物流 刘邓忠

来源:京东云开发者社区

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
2年前
java mvc 新趋势——从运行期间类扫描到编译期间
简介今天我要讲解的是主角是AnnotationProcessor,她不是什么新技术jdk1.6就存在了。AnnotationProcessor是javac的一个工具,它用来在编译时扫描和处理注解。通过AnnotationProcessor可以获取到注解和被注解对象的相关信息,然后根据注解自动生成Java代码,省去了手动编写,
kenx kenx
2年前
Java自定义注解使用和详解
前言我们在做开发springboot项目时候会遇到各种各样注解,使用各种各样注解,极大的简便了我们开发流程,方式,从JDK5开始支持注解是Java语言的一种强大的功能可以理解为代码上的特殊标记,通过这些标记我们可以在编译,类加载,运行等程序类的生命周期内被读取、执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息自定
lucien-ma lucien-ma
2年前
注解和反射
注解和反射1.注解1.1什么是注解?注解和注释的差别在于注解可以被其他程序读取1.2内置注解@Override定义在java.lang.Override中,表示一个方法声明打算重写超类中的另一个方法声明@Deprecated定义在java.lang.Deprecated中,此注解可以用于修辞方法,属性,类,表示不鼓励程序员使用这样的
Wesley13 Wesley13
2年前
java 编译时注解框架 lombok
lombokexlombokex是一款类似于lombok的编译时注解框架。编译时注,拥有运行时注解的便利性,和无任何损失的性能。主要补充一些lombok没有实现,且自己会用到的常见工具。创作目的补充lombok缺失的注解,便于日常开发使用。lombok的源码基本
Wesley13 Wesley13
2年前
JAVA注解
一、初步认识注解1.为什么学习注解?答: a.能够读懂别人写的代码,特别是框架相关的代码        b.让编程更加简洁,代码更加清晰2.注解概念?答:java提供了一种原程序中的元素关联任何信息和任何元数据的途径和方法二、java中的常见注解1.JDK自带注解:        a、@Override
Stella981 Stella981
2年前
SpotBugs注解SuppressWarnings在Java&Groovy中的应用
在最近做Java服务端代码静态测试过程中,目前采取的方案如下:测试拉取代码到本地。使用IDE:Intellij,插件:SpotBugs(无增强插件)进行静态测试,更新BUG信息,维护文档和代码中的注解。开发修复禅道BUG。QA拉取修复代码分支,与本地分支(含有抑制注解)进行合并,
Easter79 Easter79
2年前
Spring中的AOP(三)——基于Annotation的配置方式(一)
    AspectJ允许使用注解用于定义切面、切入点和增强处理,而Spring框架则可以识别并根据这些注解来生成AOP代理。Spring只是使用了和AspectJ5一样的注解,但并没有使用AspectJ的编译器或者织入器,底层依然使用SpringAOP来实现,依然是在运行时动态生成AOP代理,因此不需要增加额外的编译,也不需要AspectJ的织入器支持。
Stella981 Stella981
2年前
Babel总结
什么是babel?babel是一个JavaScript编译器。Babel是一个工具链,主要用于将ECMAScript2015代码转换为向后兼容的旧浏览器或环境中JavaScript版本。注解:传统的编译是指转化成可执行的代码,也就是二进制代码。但是对于前端来说,因为JS是解释性语言,对于浏览器或者Node来说就是可执行的代码。
Wesley13 Wesley13
2年前
Java 注解
概述注解时在Java5中开始引入的概念。可以将注解想象成标签,给指定的方法、类、变量、参数、包等贴上一个标签。!Java注解(https://oscimg.oschina.net/oscnet/6503c7a6ffdfc128c47635d91220373e1af.jpg)@Override注解就是告诉编译器,这个方法是重写的父类方
liam liam
7个月前
Swagger annotations (注解):让API文档设计更高效
提供的注解集是其框架中定义API规范和文档的重要工具。这些注解在代码里标注重要部分,为Swagger的解析工作铺路,进而生成详尽的API文档。开发者编写的注释能够被转换成直观的文档,并展现API端点、参数和响应等信息。这不仅提升了开发人员对API运作的理解