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

问题描述

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

有一个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.hqwc.cn/news/164094.html

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

相关文章

【Java】IntelliJ IDEA使用JDBC连接MySQL数据库并写入数据

目录 0 准备工作1 创建Java项目2 添加JDBC 驱动程序3 创建数据库连接配置文件4 创建一个 Java 类来连接和操作数据库5 运行应用程序 在 IntelliJ IDEA 中连接 MySQL 数据库并将数据存储在数据表中&#xff0c;使用 Java 和 JDBC&#xff08;Java Database Connectivity&#xf…

老电脑升级内存、固态硬盘、重新装机过程记录

基础环境&#xff1a; 电脑型号&#xff1a;联想XiaoXin700-15ISK系统版本&#xff1a;Windows10 家庭中文版 版本22H2内存&#xff1a;硬盘&#xff1a; 升级想法&#xff1a; 内存升级&#xff0c;固态硬盘升级&#xff0c;系统重装&#xff08;干净一点&#xff09; 升级内存…

【NLP】特征提取: 广泛指南和 3 个操作教程 [Python、CNN、BERT]

什么是机器学习中的特征提取&#xff1f; 特征提取是数据分析和机器学习中的基本概念&#xff0c;是将原始数据转换为更适合分析或建模的格式过程中的关键步骤。特征&#xff0c;也称为变量或属性&#xff0c;是我们用来进行预测、对对象进行分类或从数据中获取见解的数据点的…

unity打AB包,AssetBundle预制体与图集(三)

警告&#xff1a; spriteatlasmanager.atlasrequested wasn’t listened to while 条件一&#xff1a;图片打图集里面去了 条件二&#xff1a;然后图集打成AB包了 条件三&#xff1a;UI预制体也打到AB包里面去了 步骤一&#xff1a;先加载了图集 步骤二&#xff1a;再加载UI预…

git增加右键菜单

有次不小心清理系统垃圾&#xff0c;把git右击菜单搞没了&#xff0c;下面是恢复方法 将下面代码存为.reg文件&#xff0c;双击后导出生效&#xff0c;注意&#xff0c;你安装的git必须是默认C盘的&#xff0c;如果换了地方要改下面注册表文件中相关的位置 Windows Registry …

Mysql数据库的备份和恢复及日志管理

一、数据备份概述 1.1 备份的分类 完全备份&#xff1a;整个数据库完整地进行备份 增量备份&#xff1a;在完全备份的基础之上&#xff0c;对后续新增的内容进行备份 冷备份&#xff1a;关机备份&#xff0c;停止mysql服务&#xff0c;然后进行备份 热备份&#xff1a;开机备…

【数据开发】大数据平台架构,Hive / THive介绍

1、大数据引擎 大数据引擎是用于处理大规模数据的软件系统&#xff0c; 常用的大数据引擎包括Hadoop、Spark、Hive、Pig、Flink、Storm等。 其中&#xff0c;Hive是一种基于Hadoop的数据仓库工具&#xff0c;可以将结构化的数据映射到Hadoop的分布式文件系统上&#xff0c;并提…

SQL注入漏洞:CMS布尔盲注python脚本编写

SQL注入漏洞:CMS布尔盲注python脚本编写 文章目录 SQL注入漏洞:CMS布尔盲注python脚本编写库名爆破爆破表名用户名密码爆破 库名爆破 import requests #库名 database"" x0 while requests.get(urlf"http://10.9.47.77/cms/show.php?id33%20and%20length(data…

基于SpringBoot+Vue的点餐管理系统

基于springbootvue的点餐平台网站系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 菜品详情 个人中心 订单 管理员界面 菜品管理 摘要 点餐管理系统是一种用…

链表题(1)

链表题 今天给大家带来道链表题的练习 链表的中间节点 先给大家奉上链接&#xff1a; https://leetcode.cn/problems/middle-of-the-linked-list/description/ 题目描述; 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0…

useEffect和useLayoutEffect的区别

烤冷面加辣条的抖音 - 抖音 (douyin.com) 一、看下面的代码&#xff0c;即使调换useLayoutEffect和useEffect的位置依旧是useLayoutEffect先输出。 import { useState, useEffect, useLayoutEffect } from "react"; const Index () > {useLayoutEffect(() >…

柱状图:带误差棒

误差棒可以表示样本标准差&#xff0c;也可以表示样本标准误。 导入库&#xff1a; import pandas as pd 自定义用来绘制带误差棒&#xff08;样本标准差或样本标准误&#xff09;的柱状图&#xff1a; def col(y, x, face, df, errprbarstd) : print(ggplot(df.groupby([x…