首页 > 其他分享 >警惕!自定义注解使用不当的排查实录

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

时间:2024-05-27 14:58:12浏览次数:28  
标签:使用不当 缓存 自定义 代码 接口 调用 使用 注解

一、引言

大家好,在日常开发过程中,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注解,同时保持代码的清晰和可维护性。

 

下面举例一些日常容易出现使用不当的注解:

@Transactional :当事务注解代码范围的逻辑中有大事务、长事务情况下,可能导致数据库死锁,系统性能极速下降等风险;

@Async :如过度使用可能导致线程过多内存CPU的使用率增长较大;如代码中有阻塞操作可能导致线程无法释放等风险。

@自定义注解:当使用自定义注解(例如权限验证、参数转换等),特别是使用三方的自定义注解,一定要先了解其使用范围、使用限制等,避免因不了解而造成线上事故。

上述举例并非全部的案例,也并未详细展开,后续会再进行一些具体案例的分享,谢谢大家!

 

以上仅仅代表个人观点,一点愚见,还请大家批评指正!同时,欢迎大佬们一起来补充!

标签:使用不当,缓存,自定义,代码,接口,调用,使用,注解
From: https://www.cnblogs.com/Jcloud/p/18215478

相关文章

  • custom:用户自定义插件,提供开放能力
    custom插件的功能:支持用户在右键菜单中自定义插件。简介custom插件大量采用声明式代码(声明代替代码开发),比如:只需使用style=()=>"...",即可注册css。只需使用styleTemplate=()=>({renderArg}),即可引入css文件,并且支持渲染模板。只需使用html=()=>"...",即......
  • @Qualifier注解的作用
    @Qualifier注解在Spring框架中的作用主要是用来解决自动装配时可能出现的歧义性,也就是当有多个类型兼容的Bean可以注入到同一位置时,通过指定@Qualifier来明确注入哪一个Bean。在使用自动装配(如@Autowired)时,如果Spring容器中存在多个同类型的Bean,Spring将无法决定使用哪一个......
  • Vue3标签组件绘制--自定义按钮组件
    不知道怎么的,突然想绘制一个标签,比如el-button什么的。今天研究一下吧,不知道能不能整出来以后就可以绘制自己的组件,弄自己的组件库了。不知道有朝一日能不能让越组件青史留名?嘻嘻,百日梦做差不多了,接着去查查资料。文章分为三个部分:1.按钮组件实现(根据查阅的一个文章实现基......
  • 【C语言】自定义类型:联合与枚举的简明概述
    ......
  • Qt QListWidget 存放自定义控件不显示问题
    问题软件功能:每点击一次新建按钮,在QListWidget新增一行自定义控件,主窗口和自定义窗口如下。主窗口:自定义窗口问题代码:Form*myform=newForm();QListWidgetItem*item=newQListWidgetItem(ui->listWidget);ui->listWidget->addItem(item);......
  • 这个让ChatGPT变强的功能还有多少人不知道(自定义ChatGPT)!
    ChatGPT已经成为许多人日常生活和工作中的重要工具。为了更好地满足用户的个性化需求,ChatGPT推出了自定义功能。但是最近我发现很多同学因为嫌麻烦或者不知道怎么写,就没有使用过这个功能,也有些同学都不知道这个功能。所以今天我就来科普一下怎么使用这个功能使你的ChatGPT......
  • 自定义类型:联合和枚举
    目录1.联合体1.1联合体类型的声明1.2联合体的特点1.3相同成员的结构体和联合体对比1.4联合体大小的计算1.5联合的⼀个练习2.枚举类型2.1枚举类型的声明2.2枚举类型的优点2.3枚举类型的使用1.联合体1.1联合体类型的声明像结构体⼀样,联合体也是由⼀个或......
  • 自定义RedisTemplate,解决Redis乱码问题
    问题:使用默认的RedisTemplate来操作Redis,在其底层使用的是JDK序列化器,会导致数据乱码问题,可读性差,其优点是兼容性高。解决:自定义RedisTemplate,使用Jackson序列化器替代JDK序列化器。@ConfigurationpublicclassRedisConfig{@BeanpublicRedisTemplate<String,......
  • 【Spring】Scope注解的使用以及原理
    1 前言Spring帮助我们管理着Bean,那么带来的一个思考就是Bean该维护一个实例呢?还是每次都获取新的呢?单例的依赖多例的作用范围的变化怎么处理呢?也就是Bean的一个作用范围的管理是怎么控制的呢?这就是我们本节要看的Scope。2 Scope介绍2.1 @Scope注解在spring中,......
  • Haskell 的 自定义类型(data、type)
    在Haskell中,type和data关键字都用于定义新的数据类型,但它们有着不同的作用和语法。一、type关键字:作用:type关键字用于为已有类型创建别名,使得代码更易读和更具可读性。语法:其语法为typeNewType=ExistingType,其中NewType是新类型的名称,ExistingType是已有类......