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

问题描述

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

有一个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.mzph.cn/news/131015.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

社区牛奶智能售货机为你带来便利与实惠

社区牛奶智能售货机为你带来便利与实惠 低成本&#xff1a;社区牛奶智能货机的最大优势在于成本低廉&#xff0c;租金和人工开支都很少。大部分时间&#xff0c;货柜都是由无人操作来完成销售任务。 购买便利&#xff1a;社区居民只需通过手机扫码支付&#xff0c;支付后即可自…

二、计算机组成原理与体系结构

&#xff08;一&#xff09;数据的表示 不同进制之间的转换 R 进制转十进制使用按权展开法&#xff0c;其具体操作方式为&#xff1a;将 R 进制数的每一位数值用 Rk 形式表示&#xff0c;即幂的底数是 R &#xff0c;指数为 k &#xff0c;k 与该位和小数点之间的距离有关。当…

【PyTorch实战演练】AlexNet网络模型构建并使用Cifar10数据集进行批量训练(附代码)

目录 0. 前言 1. Cifar10数据集 2. AlexNet网络模型 2.1 AlexNet的网络结构 2.2 激活函数ReLu 2.3 Dropout方法 2.4 数据增强 3. 使用GPU加速进行批量训练 4. 网络模型构建 5. 训练过程 6. 完整代码 0. 前言 按照国际惯例&#xff0c;首先声明&#xff1a;本文只是我…

[开源]企业级在线办公系统,基于实时音视频完成在线视频会议功能

一、开源项目简介 企业级在线办公系统 本项目使用了SpringBootMybatisSpringMVC框架&#xff0c;技术功能点应用了WebSocket、Redis、Activiti7工作流引擎&#xff0c; 基于TRTC腾讯实时音视频完成在线视频会议功能。 二、开源协议 使用GPL-3.0开源协议 三、界面展示 部分…

2024天津理工大学中环信息学院专升本机械设计制造自动化专业考纲

2024年天津理工大学中环信息学院高职升本科《机械设计制造及其自动化》专业课考试大纲《机械设计》《机械制图》 《机械设计》考试大纲 教 材&#xff1a;《机械设计》&#xff08;第十版&#xff09;&#xff0c;高等教育出版社&#xff0c;濮良贵、陈国定、吴立言主编&#…

1.如何实现统一的API前缀-web组件篇

文章目录 1. 问题的由来2.实现原理3. 总结 1. 问题的由来 系统提供了 2 种类型的用户&#xff0c;分别满足对应的管理后台、用户 App 场景。 两种场景的前缀不同&#xff0c;分别为/admin-api/和/app-api/&#xff0c;都写在一个controller里面&#xff0c;显然比较混乱。分开…

AI:60-基于深度学习的瓜果蔬菜分类识别

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

网络基础扫盲-多路转发

博客内容&#xff1a;多路转发的常见方式select&#xff0c;poll&#xff0c;epoll 文章目录 一、五种IO模型二、多路转发的常见接口1.select2、poll3、epoll 总结 前言 Linux下一切皆文件&#xff0c;是文件就会存在IO的情况&#xff0c;IO的方式决定了效率的高低。 一、五种…

基于java+springboot+vue在线选课系统

项目介绍 本系统结合计算机系统的结构、概念、模型、原理、方法&#xff0c;在计算机各种优势的情况下&#xff0c;采用JAVA语言&#xff0c;结合SpringBoot框架与Vue框架以及MYSQL数据库设计并实现的。员工管理系统主要包括个人中心、课程管理、专业管理、院系信息管理、学生…

Cube MX 开发高精度电流源跳坑过程/SPI连接ADS1255/1256系列问题总结/STM32 硬件SPI开发过程

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 1.使用STM32F系列开发一款高精度恒流电源&#xff0c;用到了24位高精度采样芯片ADS1255/ADS1256系列。 2.使用时发现很多的坑&#xff0c;详细介绍了每个坑的具体情况和实际的解决办法。 坑1&#xff1a;波特率设置…

如何使用Ruby 多线程爬取数据

现在比较主流的爬虫应该是用python&#xff0c;之前也写了很多关于python的文章。今天在这里我们主要说说ruby。我觉得ruby也是ok的&#xff0c;我试试看写了一个爬虫的小程序&#xff0c;并作出相应的解析。 Ruby中实现网页抓取&#xff0c;一般用的是mechanize&#xff0c;使…

Pytorch从零开始实战08

Pytorch从零开始实战——YOLOv5-C3模块实现 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——YOLOv5-C3模块实现环境准备数据集模型选择开始训练可视化模型预测总结 环境准备 本文基于Jupyter notebook&#xff0c;使用Python3.8&#xff0c…

webJS基础-----制作一个时间倒计时

1&#xff0c;可以使用以下两个方式制作 方式1&#xff1a;setTimeout ()定时器是在指定的时间后执行某些代码&#xff0c;代码执行一次就会自动停止&#xff1b; 方式2&#xff1a;setInterval ()定时器是按照指定的周期来重复执行某些代码&#xff0c;该定时器不会自动停止…

DL Homework 6

目录 一、概念 &#xff08;1&#xff09;卷积 &#xff08;2&#xff09;卷积核 &#xff08;3&#xff09;特征图 &#xff08;4&#xff09;特征选择 &#xff08;5&#xff09;步长 &#xff08;6&#xff09;填充 &#xff08;7&#xff09;感受野 二、探究不同卷…

JVM运行时数据区-堆

目录 一、堆的核心概述 &#xff08;一&#xff09;概述 &#xff08;二&#xff09;堆空间细分 &#xff08;三&#xff09;jvisualvm工具 二、设置堆内存的大小与OOM 三、年轻代与老年代 四、图解对象分配一般过程 五、对象分配特殊过程 六、常用调优工具 七、Mino…

leetCode 416.分割等和子集 + 01背包 + 动态规划 + 记忆化搜索 + 递推 + 空间优化

关于此题我的往期文章&#xff1a; LeetCode 416.分割等和子集&#xff08;动态规划【0-1背包问题】采用一维数组dp:滚动数组&#xff09;_呵呵哒(&#xffe3;▽&#xffe3;)"的博客-CSDN博客https://heheda.blog.csdn.net/article/details/133212716看本期文章时&…

关于JADX和JEB的小问题

关于JADX和JEB的小问题 很久没水过技术文啦&#xff0c;最近也刚好遇到点小问题&#xff0c;特此记录 第一个问题 在处理app加密逻辑的时候一直拿不到正确的密文&#xff0c;反复看了反编译出来的代码&#xff08;如下图&#xff09; public static string n(String str, Stri…

基础课22——云服务(SaaS、Pass、laas、AIaas)

1.云服务概念和类型 云服务是一种基于互联网的计算模式&#xff0c;通过云计算技术将计算、存储、网络等资源以服务的形式提供给用户&#xff0c;用户可以通过网络按需使用这些资源&#xff0c;无需购买、安装和维护硬件设备。云服务具有灵活扩展、按需使用、随时随地访问等优…

linux 查看当前目录下每个文件夹大小

要在 Linux 中查看当前目录下每个文件夹的大小&#xff0c;可以使用 du 命令&#xff08;磁盘使用情况&#xff09;结合其他一些选项。下面是几个常用的命令示例&#xff1a; 显示当前目录下每个文件夹的大小——只显示一层文件夹&#xff1a; du -h --max-depth1该命令会以人…

2023年内衣行业分析:京东大数据平台-服饰内衣市场解析

如今&#xff0c;女性消费力的提升正在推动国内女性内衣市场份额逐年提升。而今年&#xff0c;内衣市场更是进入了存量之战&#xff0c;增长趋势明显减弱。 根据鲸参谋数据显示&#xff0c;今年1月至9月&#xff0c;京东平台内衣&#xff08;文胸&#xff09;累计销量约500万件…