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

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

相关文章

使用Python自建轻量级的HTTP调试工具

《使用Python自建轻量级的HTTP调试工具》这篇文章主要为大家详细介绍了如何使用Python自建一个轻量级的HTTP调试工具,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录一、为什么需要自建工具二、核心功能设计三、技术选型四、分步实现五、进阶优化技巧六、使用示例七、性能对比八、扩展方向建

使用Python实现一键隐藏屏幕并锁定输入

《使用Python实现一键隐藏屏幕并锁定输入》本文主要介绍了使用Python编写一个一键隐藏屏幕并锁定输入的黑科技程序,能够在指定热键触发后立即遮挡屏幕,并禁止一切键盘鼠标输入,这样就再也不用担心自己... 目录1. 概述2. 功能亮点3.代码实现4.使用方法5. 展示效果6. 代码优化与拓展7. 总结1.

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat

C++中std::distance使用方法示例

《C++中std::distance使用方法示例》std::distance是C++标准库中的一个函数,用于计算两个迭代器之间的距离,本文主要介绍了C++中std::distance使用方法示例,具... 目录语法使用方式解释示例输出:其他说明:总结std::distance&n编程bsp;是 C++ 标准

vue使用docxtemplater导出word

《vue使用docxtemplater导出word》docxtemplater是一种邮件合并工具,以编程方式使用并处理条件、循环,并且可以扩展以插入任何内容,下面我们来看看如何使用docxtempl... 目录docxtemplatervue使用docxtemplater导出word安装常用语法 封装导出方

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.