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

news2024/11/28 2:49:12

问题描述

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

有一个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 {
        //资产1
        STAGE_CODE.put(AssetTypeEnum.ZICHAN_1.getCode(),
                new HashMap<String, String>() {{
                    put("JS", "ZICHAN1-JS");
                }});
        //资产2
        STAGE_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.coloradmin.cn/o/1172696.html

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

相关文章

python算法例6 快速幂

1. 问题描述 计算&#xff0c;其中a、b和n都是32位的非负整数。 2. 问题示例 例如&#xff1a;。 3.代码实现 计算a的n次幂对b取余&#xff0c;可以使用快速幂算法。这个算法通过减少乘法和取余操作的次数来提高效率。 def pow_mod(a, n, b):result 1while n > 0:if …

园区网真实详细配置大全案例

实现要求&#xff1a; 1、只允许行政部电脑对全网telnet管理 2、所有dhcp都在核心 3、wifi用户只能上外网&#xff0c;不能访问局域网其它电脑 4、所有接入交换机上bpdu保护 5、只允许vlan 10-40上网 5、所有接入交换机开dhcp snoop 6、所有的交换机指定核心交换机为ntp时间服务…

【Unity数据交互】游戏中常用到的Json序列化

ˊˊ &#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1…

独创改进 | RT-DETR 引入双向级联特征融合结构 RepBi-PAN | 附手绘结构图原图

本专栏内容均为博主独家全网首发,未经授权,任何形式的复制、转载、洗稿或传播行为均属违法侵权行为,一经发现将采取法律手段维护合法权益。我们对所有未经授权传播行为保留追究责任的权利。请尊重原创,支持创作者的努力,共同维护网络知识产权。 文章目录 YOLOv6贡献RepBi-…

面向网络安全-Python语言

目录 1、变量 2、字符串 3、列表 4、字典 5、网络 6、条件选择语句 7、异常处理 1、变量 变量是指储存在某个内存地址上的数据 主要有&#xff1a;整型数、实数、布尔值、字符串、列表、元组、字典 这些数据在声明后&#xff0c;解释器就会自动确定每个变量的类型&…

【云原生 | Docker】Linux 定时自动化备份Mysql数据到本地 Windows 最佳实践,确定不来看看?

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

C#,数值计算——偏微分方程,Mgfas的计算方法与源程序

1 文本格式 using System; using System.Collections.Generic; namespace Legalsoft.Truffer { public class Mgfas { public int n { get; set; } public int ng { get; set; } public double[,] uj; public double[,] uj1 { get; …

云安全-攻防视角下如何看待堡垒机

0x00 堡垒机简介 堡垒机是种网络安全设备&#xff0c;用于保护和管理企业内部网络与外部网络之间的访问。它作为一种中间节点&#xff0c;提供安全的访问控制和审计功能&#xff0c;用于保护内部网络免受未经授权的访问和攻击。堡垒机通常被用作跳板服务器&#xff0c;即堡垒机…

2023辽宁省数学建模A题铁路车站的安全标线完整原创论文详细讲解(含matlab代码)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了辽宁省数学建模A题完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 B预计下午两点前更新完毕&#xff0c;A全…

【错误解决方案】TypeError: gca() got an unexpected keyword argument ‘projection‘

1. 错误提示 在 python程序中&#xff0c;使用Matplotlib库中的 gca() 函数出现错误&#xff0c;提示“TypeError: gca() got an unexpected keyword argument projection”。 2. 解决方案 这个错误的原因是 gca() 函数并不接受 projection 这个关键字参数。 gca() 函数是用来…

半导体芯片制造行业MES系统解决方案

半导体产业作为现代电子科技的重要支柱&#xff0c;驱动着电子设备和通信技术的飞速发展。随着技术不断演进&#xff0c;半导体制造企业面临着越来越多的挑战&#xff0c;如高度复杂的工艺流程、全球化的竞争、质量控制的要求以及能源效率等问题。 为了应对这些挑战&#xff0…

学电脑编程零基础,计算机编程入门先学什么

学电脑编程零基础&#xff0c;计算机编程入门先学什么&#xff0c;建议先从容易学习的语言入手&#xff0c;比如中文编程。 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&…

第六章 Python函数

系列文章目录 第一章 Python 基础知识 第二章 python 字符串处理 第三章 python 数据类型 第四章 python 运算符与流程控制 第五章 python 文件操作 第六章 python 函数 第七章 python 常用内建函数 第八章 python 类(面向对象编程) 第九章 python 异常处理 第十章 python 自定…

美颜SDK哪家便宜?企业级美颜工具包

在当今竞争激烈的企业宣传领域&#xff0c;一张优质的照片可以为企业带来巨大的价值。然而&#xff0c;由于各种原因&#xff0c;拍摄的照片、视频往往存在一些瑕疵&#xff0c;如肤色不均、背景杂乱等。为了解决这个问题&#xff0c;美摄科技特别推出了面向企业的专业美颜SDK&…

大数据之LibrA数据库系统告警处理(ALM-12017 磁盘容量不足)

告警解释 系统每30秒周期性检测磁盘使用率&#xff0c;并把磁盘使用率和阈值相比较。磁盘使用率有一个默认阈值&#xff0c;当检测到磁盘使用率超过阈值时产生该告警。 平滑次数为1&#xff0c;主机磁盘某一分区使用率小于或等于阈值时&#xff0c;告警恢复&#xff1b;平滑次…

当 Next.js 遇到 Wordpress 会发生什么?

Wordpress 作为一款小型的内容管理系统&#xff0c;受到很多站长的欢迎&#xff0c;特别是作为个人博客&#xff0c;丰富的插件和主题提供了更多的定制和自由度。 自从前后端分离的开发模式兴起之后&#xff0c;Wordpress 又被作为数据存储的后端服务提供接口给第三方的前端页面…

JVM类的声明周期

文章目录 版权声明生命周期概述加载阶段查看内存中的对象 连接阶段连接阶段之验证连接阶段之准备连接阶段之解析 初始化阶段练习题目一练习题目二练习题目三练习题目四 使用阶段卸载阶段总结 版权声明 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明…

第四章 Python运算符与流程控制

系列文章目录 第一章 Python 基础知识 第二章 python 字符串处理 第三章 python 数据类型 第四章 python 运算符与流程控制 第五章 python 文件操作 第六章 python 函数 第七章 python 常用内建函数 第八章 python 类(面向对象编程) 第九章 python 异常处理 第十章 python 自定…

【Unity之UI编程】如何用UGUI搭建一个登录注册面板

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;UI_…