成员变量为动态数据时不可轻易使用

2023-11-06 03:30

本文主要是介绍成员变量为动态数据时不可轻易使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

问题描述

业务验收阶段,遇到了一个由于成员变量导致的线程问题

有一个kafka切面,用来处理某些功能在调用前后的发送消息,资产类型type是成员变量定义;

资产1类型推送消息是以zichan1为节点;资产2类型推送消息是以zichan2为节点;

当多个线程调用切面类时,由于切面类中使用成员变量且为动态数据时,此时会出现根据资产类型推送消息错误;例如资产1在调用功能时,切面类的type字段为zichan1;同时有资产2调用功能时,此时的切面类的type字段为zichan2;导致资产1在调用功能前推送的是zichan1,在调用功能后推送的是zichan2的消息标识。

原因

当多个线程同时调用时,成员变量则会只采用最后一次调用的值。

下面简单描述下情景:

kafkaAspect类

import com.alibaba.fastjson.JSON;
import com.example.demo.constant.StageCodeConstant;
import com.example.demo.entity.KafkaSendMessageConstant;
import com.example.demo.util.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Component
@Aspect
@Slf4j
public class KafkaProcessAspect {private static final Logger log = LoggerFactory.getLogger(KafkaProcessAspect.class);private String assetType = "";private String startTime = "";@Around("@annotation(kafkaProcess)")public Object doAround(ProceedingJoinPoint joinPoint, KafkaProcess kafkaProcess) throws Throwable {Object object = null;assetType = (String) getParamValue(joinPoint, "assetType");startTime = DateUtils.getTime();object = joinPoint.proceed();//推送消息String stageCode = StageCodeConstant.STAGE_CODE.get(assetType).get(kafkaProcess.functionName());KafkaSendMessageConstant messageConstant = new KafkaSendMessageConstant();messageConstant.setStageCode(stageCode);messageConstant.setStartTime(startTime);messageConstant.setEndTime(DateUtils.getTime());log.info("资产类型{}推送消息:{}", assetType, JSON.toJSONString(messageConstant));return object;}private Object getParamValue(ProceedingJoinPoint joinPoint, String paramName) {Object[] params = joinPoint.getArgs();String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();if (parameterNames == null || parameterNames.length == 0) {return null;}for (String param : parameterNames) {if (param.equals(paramName)) {int index = ArrayUtils.indexOf(parameterNames, param);return params[index];}}return null;}}

 由于controller中的请求地址是采用占位符定义,后使用@PathVariable可以获取的类型

Controller类

import com.example.demo.aop.KafkaProcess;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/test")
public class TestController {private static final Logger logger = LoggerFactory.getLogger(TestController.class);@PostMapping("/{assetType}/cmpt")@KafkaProcess(functionName = "JS")public void cmpt(@PathVariable("assetType") String assetType) {logger.info("{}接口开始", assetType);long startTime = System.currentTimeMillis();for (int i = 0; i < 20000; i++) {for (int j = 0; j < 20000; j++) {int m = i * j;logger.debug("i*j={}",m);}}long endTime = System.currentTimeMillis();logger.info("{}接口结束,耗时{}", assetType, endTime - startTime);}}

 资产类型枚举 AssetTypeEnum

public enum AssetTypeEnum {ZICHAN_1("zichan1","资产1"),ZICHAN_2("zichan2","资产2");// 成员变量private String code;private String desc;public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}// 构造方法AssetTypeEnum(String code,String desc) {this.code = code;this.desc = desc;}}

推送消息 KafkaSendMessageConstant

public class KafkaSendMessageConstant {private String stageCode;private String startTime;private String endTime;public String getStageCode() {return stageCode;}public void setStageCode(String stageCode) {this.stageCode = stageCode;}public String getStartTime() {return startTime;}public void setStartTime(String startTime) {this.startTime = startTime;}public String getEndTime() {return endTime;}public void setEndTime(String endTime) {this.endTime = endTime;}
}

节点常量类 StageCodeConstant ;根据不同资产类型赋值不同功能的推送标识

import java.util.HashMap;
import java.util.Map;public class StageCodeConstant {public static final Map<String, Map<String, String>> STAGE_CODE = new HashMap<>();static {//资产1STAGE_CODE.put(AssetTypeEnum.ZICHAN_1.getCode(),new HashMap<String, String>() {{put("JS", "ZICHAN1-JS");}});//资产2STAGE_CODE.put(AssetTypeEnum.ZICHAN_2.getCode(),new HashMap<String, String>() {{put("JS", "ZICHAN2-JS");}});}
}

用postman调用资产2接口

后立即调用资产1接口

此时出现这种结果:

会发现,调用资产2的时候发送消息还是资产1的信息;然后资产1发送的消息也是资产1的信息

解决

此时有两个解决办法,一个是将doAround()和其他方法合并为一个方法,将成员变量调整为局部变量;另一个则为将该成员变量设置为一个对象,对这个对象进行线程设置,保证doAround()和doBefore()获取的是同一个对象的数据

解决方法一

import com.alibaba.fastjson.JSON;
import com.example.demo.constant.StageCodeConstant;
import com.example.demo.entity.KafkaSendMessageConstant;
import com.example.demo.util.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Component
@Aspect
@Slf4j
public class KafkaProcessAspect {private static final Logger log = LoggerFactory.getLogger(KafkaProcessAspect.class);@Around("@annotation(kafkaProcess)")public Object doAround(ProceedingJoinPoint joinPoint, KafkaProcess kafkaProcess) throws Throwable {Object object = null;String assetType = (String) getParamValue(joinPoint, "assetType");String startTime = DateUtils.getTime();object = joinPoint.proceed();//推送消息String stageCode = StageCodeConstant.STAGE_CODE.get(assetType).get(kafkaProcess.functionName());KafkaSendMessageConstant messageConstant = new KafkaSendMessageConstant();messageConstant.setStageCode(stageCode);messageConstant.setStartTime(startTime);messageConstant.setEndTime(DateUtils.getTime());log.info("资产类型{}推送消息:{}", assetType, JSON.toJSONString(messageConstant));return object;}private Object getParamValue(ProceedingJoinPoint joinPoint, String paramName) {Object[] params = joinPoint.getArgs();String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();if (parameterNames == null || parameterNames.length == 0) {return null;}for (String param : parameterNames) {if (param.equals(paramName)) {int index = ArrayUtils.indexOf(parameterNames, param);return params[index];}}return null;}}

解决方法二

成员变量KafkaSingleDTO

public class KafkaSingleDTO {private String assetType="";private String date="";public String getAssetType() {return assetType;}public void setAssetType(String assetType) {this.assetType = assetType;}public String getDate() {return date;}public void setDate(String date) {this.date = date;}
}

对象单实例获取 KafkaSingleUtil

import com.example.demo.entity.KafkaSingleDTO;public class KafkaSingleUtil {private static ThreadLocal<KafkaSingleDTO> START = new ThreadLocal<>();public static KafkaSingleDTO getObject() {KafkaSingleDTO singleDTO = START.get();if (singleDTO == null) {singleDTO = new KafkaSingleDTO();}return singleDTO;}}

kafkaAsspect拦截器

import com.alibaba.fastjson.JSON;
import com.example.demo.constant.StageCodeConstant;
import com.example.demo.entity.KafkaSendMessageConstant;
import com.example.demo.entity.KafkaSingleDTO;
import com.example.demo.util.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Component
@Aspect
@Slf4j
public class KafkaProcessAspect {private static final Logger log = LoggerFactory.getLogger(KafkaProcessAspect.class);private static String assetType="";private static String startTime="";@Around("@annotation(kafkaProcess)")public Object doAround(ProceedingJoinPoint joinPoint, KafkaProcess kafkaProcess) throws Throwable {Object object = null;assetType = (String) getParamValue(joinPoint, "assetType");startTime = DateUtils.getTime();KafkaSingleDTO singleDTO = KafkaSingleUtil.getObject();singleDTO.setAssetType(assetType);singleDTO.setDate(startTime);object = joinPoint.proceed();//推送消息--方法调用后String stageCode = StageCodeConstant.STAGE_CODE.get(singleDTO.getAssetType()).get(kafkaProcess.functionName());KafkaSendMessageConstant messageConstant = new KafkaSendMessageConstant();messageConstant.setStageCode(stageCode);messageConstant.setStartTime(singleDTO.getDate());messageConstant.setEndTime(DateUtils.getTime());log.info("资产类型{}方法后推送消息:{}", singleDTO.getAssetType(), JSON.toJSONString(messageConstant));return object;}@Before("@annotation(kafkaProcess)")public void doBefore(KafkaProcess kafkaProcess){//推送消息--方法调用前KafkaSingleDTO singleDTO = KafkaSingleUtil.getObject();singleDTO.setAssetType(assetType);String stageCode = StageCodeConstant.STAGE_CODE.get(singleDTO.getAssetType()).get(kafkaProcess.functionName());KafkaSendMessageConstant messageConstant = new KafkaSendMessageConstant();messageConstant.setStageCode(stageCode);//...消息实体类 可自补充log.info("资产类型{}方法前推送消息:{}", singleDTO.getAssetType(), JSON.toJSONString(messageConstant));}private Object getParamValue(ProceedingJoinPoint joinPoint, String paramName) {Object[] params = joinPoint.getArgs();String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();if (parameterNames == null || parameterNames.length == 0) {return null;}for (String param : parameterNames) {if (param.equals(paramName)) {int index = ArrayUtils.indexOf(parameterNames, param);return params[index];}}return null;}
}

最终结果:

到此结束!

这篇关于成员变量为动态数据时不可轻易使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/354229

相关文章

Spring Boot整合Redis注解实现增删改查功能(Redis注解使用)

《SpringBoot整合Redis注解实现增删改查功能(Redis注解使用)》文章介绍了如何使用SpringBoot整合Redis注解实现增删改查功能,包括配置、实体类、Repository、Se... 目录配置Redis连接定义实体类创建Repository接口增删改查操作示例插入数据查询数据删除数据更

使用python生成固定格式序号的方法详解

《使用python生成固定格式序号的方法详解》这篇文章主要为大家详细介绍了如何使用python生成固定格式序号,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... 目录生成结果验证完整生成代码扩展说明1. 保存到文本文件2. 转换为jsON格式3. 处理特殊序号格式(如带圈数字)4

Java使用Swing生成一个最大公约数计算器

《Java使用Swing生成一个最大公约数计算器》这篇文章主要为大家详细介绍了Java使用Swing生成一个最大公约数计算器的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下... 目录第一步:利用欧几里得算法计算最大公约数欧几里得算法的证明情形 1:b=0情形 2:b>0完成相关代码第二步:加

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV