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

问题描述

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

有一个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;
    }
}

最终结果:

到此结束!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/118066.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在现在大环境下如何回到月薪过万的软件测试工程师?

测试工程师这个岗位对于有些人来说&#xff0c;可能月薪过万很容易&#xff0c;可对于有些人来说&#xff0c;仿佛已经达到瓶颈&#xff0c;任凭工作再卖力每月也只是四五千的薪资&#xff0c;月入过万对于这些人来说就是可望不可即&#xff0c;那么这些人怎么才能冲破瓶颈&…

Docker学习——④

文章目录 1、Docker Image&#xff08;镜像&#xff09;2、镜像命令详解2.1 docker rmi2.2 docker save2.3 docker load2.4 docker image inspect2.5 docker history2.6 docker image prune 3、镜像综合实战3.1 离线镜像迁移3.2 镜像存储的压缩与共享 1、Docker Image&#xff…

Flask(Jinja2) 服务端模板注入漏洞(SSTI)

Flask&#xff08;Jinja2&#xff09; 服务端模板注入漏洞(SSTI) 参考 https://www.freebuf.com/articles/web/260504.html 验证漏洞存在 ?name{{7*7}} 回显49说明漏洞存在 vulhub给出的payload: {% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__…

UE4 Niagara Module Script 初次使用笔记

这里可以创建一个Niagara模块脚本 创建出来长这样 点击号&#xff0c;输出staticmesh&#xff0c;点击它 这样就可以拿到对应的一些模型信息 这里的RandomnTriCoord是模型的坐标信息 根据坐标信息拿到位置信息 最后的Position也是通过Map Set的号&#xff0c;选择Particles的P…

【年底不想背锅!网络工程师必收藏的排障命令大全】

网络故障排除工具是每个网络工程师的必需品。 为了提升我们的工作效率&#xff0c; 不浪费时间&#xff0c;工具的重要性显而易见 特别是每当添加新的设备或网络发生变更时&#xff0c;新的问题就会出现&#xff0c;而且很难快速确定问题出在哪里。每一位网络工程师或从事网…

【MySQL--->索引】

文章目录 [TOC](文章目录) 一、索引概念二、B树与B树1.B树的特点:2.B树的特点:3.为什么使用B树而不使用B树 三、聚簇索引和非聚簇索引四、索引操作1.创建索引2. 删除索引3.全文索引 一、索引概念 mysql的查询的过程是从文件中提取到内存中查询,MySQL启动时会在内存中维护一个b…

基于 NGram 分词,优化 Es 搜索逻辑,并深入理解了 matchPhraseQuery 与 termQuery

基于 NGram 分词&#xff0c;优化 Es 搜索逻辑&#xff0c;并深入理解了 matchPhraseQuery 与 termQuery 前言问题描述排查索引库分词&#xff08;发现问题&#xff09;如何去解决这个问题&#xff1f;IK 分词器NGram 分词器使用替换 NGram 分词器后进行测试matchPhraseQuery 查…

pytorch加载的cifar10数据集,到底有没有经过归一化

pytorch加载cifar10的归一化 pytorch怎么加载cifar10数据集torchvision.datasets.CIFAR10transforms.Normalize()进行归一化到底在哪里起作用&#xff1f;【CIFAR10源码分析】 torchvision.datasets加载的数据集搭配Dataloader使用model.train()和model.eval() pytorch怎么加载…

Webpack的Tree Shaking。它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

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

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

使用Java AOP实现面向切面编程

简介 面向切面编程&#xff08;AOP&#xff09;是一种编程思想&#xff0c;它将程序中的关注点分离&#xff0c;使得开发人员可以专注于核心业务逻辑而不必过多关注横切关注点。Java中的AOP可以通过使用AspectJ等框架来实现&#xff0c;本文将介绍如何使用Java AOP实现切面编程…

【MongoDB】索引 - 复合索引

一、准备工作 这里准备一些学生数据 db.students.insertMany([{ _id: 1, name: "张三", age: 20, class: { id: 1, name: "1班" }},{ _id: 2, name: "李四", age: 22, class: { id: 2, name: "2班" }},{ _id: 3, name: "王五…

[MRCTF2020]你传你呢1

提示 只对php以及phtml文件之类的做了防护content-type.htaccess文件 这里就不整那么麻烦直接抓包测试 首先对后缀测试看过滤了哪些 (php php3 pht php5 phtml phps) 全部被ban了 到这里的后续思路通过上传一些配置文件把上传的图片都以php文件执行 尝试上传图片码, 直接上传成…

机器人制作开源方案 | 管内检测维护机器人

一、作品简介 作者&#xff1a;李泽彬&#xff0c;李晋晟&#xff0c;杜张坤&#xff0c;禹馨雅 单位&#xff1a;运城学院 指导老师&#xff1a;薛晓峰 随着我国的社会主义市场经济的飞速发展和科学技术的革新&#xff0c;各行各业的发展越来越离不开信息化和网络化的…

虚拟机备份中的CBT技术

虚拟机备份的CBT&#xff08; Changed Block Tracking&#xff09;模式是一种备份模式&#xff0c;它能够识别和跟踪自上次备份后虚拟机中被修改过的块&#xff0c;这些修改会被存放到日志文件中。在启用CBT模式之后&#xff0c;备份软件会利用这个功能进行增量备份。 启用CBT…

高效解决香港服务器负载过高的方法

​  当我们在使用香港服务器时&#xff0c;有时会遇到服务器负载过高的问题。这会导致网站加载速度变慢甚至无法正常使用。为了解决这个问题&#xff0c;我们需要采取一些高效的方法来提升服务器的负载能力。 1.考虑对服务器进行升级维护。通过增加硬件资源&#xff0c;如CPU…

单点登录与OAuth2.0 的区别

前言&#xff1a;SSO是Single Sign On(单点登录)的缩写&#xff0c;OAuth是Open Authority&#xff08;开放授权&#xff09;&#xff0c;这两者都是使用令牌的方式来代替用户密码访问应用。流程上来说他们非常相似&#xff0c;但概念上又十分不同。很多人会将其混为一谈&#…

Oracle安全基线检查

一、账户安全 1、禁止SYSDBA用户远程连接 用户具备数据库超级管理员(SYSDBA)权限的用户远程管理登录SYSDBA用户只能本地登录,不能远程。REMOTE_LOGIN_PASSWORDFILE函数的Value值为NONE。这意味着禁止共享口令文件,只能通过操作系统认证登录Oracle数据库。 1)检查REMOTE…

noip模拟赛多校第八场 T3 遥控机器人 (最短路 + 技巧拆点)

题面 简要题意&#xff1a; 给你一个 n n n 个点 m m m 条边的图。边 i i i 有颜色 c i c_i ci​。你可以选择一些边改变它们的颜色成为区间 [ 1 , m ] [1, m] [1,m] 中的任意颜色&#xff0c;改变一条边 i i i 一次的代价是 w i w_i wi​。询问你能否在一些改变…

深度学习框架TensorFlow.NET之数据类型及张量2(C#)

环境搭建参考&#xff1a; 深度学习框架TensorFlow.NET环境搭建1&#xff08;C#&#xff09;-CSDN博客 由于本文作者水平有限&#xff0c;如有写得不对的地方&#xff0c;往指出 声明变量&#xff1a;tf.Variable 声明常量&#xff1a;tf.constant 下面通过代码的方式进行学…