当前位置: 首页 > news >正文

【黑马点评】已解决java.lang.NullPointerException异常

Redis学习Day3——黑马点评项目工程开发`-CSDN博客

问题发现及描述

        在黑马点评项目中,进行到使用Redis提供的Stream消息队列优化异步秒杀问题时,我在进行jmeter测试时遇到了重大的错误

发现无论怎么测试,一定会进入到catch中,又由于消息队列是个循环读的过程,所以ERROR 33016错误就会不断的发生。

观察一下报错信息

java.lang.NullPointerException: Cannot invoke "com.hmdp.service.IVoucherOrderService.createVoucherOrder(com.hmdp.entity.VoucherOrder)" because the return value of "com.hmdp.service.impl.VoucherOrderServiceImpl.access$400(com.hmdp.service.impl.VoucherOrderServiceImpl)" is null

意思是

java.lang.NullPointerException 错误表明你的代码中有一个地方尝试调用了 null 对象的方法或访问了其属性。在你的具体错误信息中,问题出现在尝试调用 com.hmdp.service.IVoucherOrderService.createVoucherOrder(com.hmdp.entity.VoucherOrder) 方法时,但这个方法的调用是通过 com.hmdp.service.impl.VoucherOrderServiceImpl.access$400(com.hmdp.service.impl.VoucherOrderServiceImpl) 返回的对象进行的,而这个返回值为 null。

问题排除

既然明白了问题缘由是空对象导致出来的,那我们就根据报错的栈信息去处理:

定位位置

at com.hmdp.service.impl.VoucherOrderServiceImpl$VoucherOrderHandler.handleVocherOrder(VoucherOrderServiceImpl.java:406) ~[classes/:na]

at com.hmdp.service.impl.VoucherOrderServiceImpl$VoucherOrderHandler.handlePendingList(VoucherOrderServiceImpl.java:438) ~[classes/:na]

at com.hmdp.service.impl.VoucherOrderServiceImpl$VoucherOrderHandler.run(VoucherOrderServiceImpl.java:385) ~[classes/:na]

发现定位出现问题的是 执行订单创建方法  handleVocherOrder()

 跟进去看看,proxy代理对象也是一个报错提示点

结论         

        哦,这么一来问题就解决啦!原来是由于handleVocherOrder()需要使用到代理对象进行订单创建,那他必须不能写在线程任务了,要不然是没有办法获取到代理对象的,也就是null。就是因为这个空,才导致了我们的程序一致在报错。

错误代码说明

        一开始,为了代码逻辑的顺畅可懂,我将方法进行编号,并统一写入了线程任务VoucherOrderHandler方法中,在我看来handleVocherOrder()创建订单方法 和 handlePendingList()执行异常方法 对应着两者情况,本身的地位是一致的,于是乎将其都写在了线程的内部。

        但是没注意到的是,handleVocherOrder()需要调用在主线程提供的代理对象,这样一来就没理由将它写在异步线程任务中了。

//3. 创建线程任务用于接收消息队列的信息private class VoucherOrderHandler implements Runnable{// 消息队列名称private String queueName = "stream.orders";@Overridepublic void run() {while (true) {try{//1. 获取队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.oredes >// 指定队列名称,组名称,消费者名称,读取模式,读取数量,阻塞时间,队列名称,读取位置List<MapRecord<String,Object,Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(queueName, ReadOffset.lastConsumed()));//2. 判断消息获取是否成功if( list == null || list.isEmpty()){//2.1 获取失败 说明没有消息 ---->继续循环continue;}// 解析消息中的订单信息MapRecord<String,Object,Object> record = list.get(0);//  获取键值对集合Map<Object,Object> values = record.getValue();// 获取订单信息VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);//3. 获取成功,执行订单创建handleVocherOrder(voucherOrder);//4. ACK确认  SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName,"g1",record.getId());}catch (Exception e) {// 消息没有被ACK确认 进入Pending Listlog.error("订单处理出现异常",e);handlePendingList();}}}// 4. 取到了订单—————创建订单private void handleVocherOrder(VoucherOrder voucherOrder){// 获取用户Long userId = voucherOrder.getUserId();// 1. 创建锁对象RLock lock =  redissonClient.getLock("lock:order:" + userId);//2. 尝试获取锁boolean isLock = lock.tryLock();// 3. 判断锁是否获取成功if(! isLock){log.error("不允许重复下单");}try {proxy.createVoucherOrder(voucherOrder);} finally {// 4. 释放锁lock.unlock();}}// 5.取不到订单————— 处理Pending List中的订单信息private void handlePendingList(){while (true) {try {//1. 获取Pending List中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1  STREAMS stream.oredes 0// 指定队列名称,组名称,消费者名称,读取模式,读取数量,阻塞时间,队列名称,读取位置List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1),StreamOffset.create(queueName, ReadOffset.from("0")));//2. 判断消息获取是否成功if (list == null || list.isEmpty()) {//2.1 获取失败 说明Pending List没有消息 ---->结束循环break;}// 解析消息中的订单信息MapRecord<String, Object, Object> record = list.get(0);//  获取键值对集合Map<Object, Object> values = record.getValue();// 获取订单信息VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);//3. 获取成功,执行订单创建handleVocherOrder(voucherOrder);//4. ACK确认  SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName, "g1", record.getId());} catch (Exception e) {log.error("Pending List订单处理出现异常", e);try {Thread.sleep(20);}catch (InterruptedException interruptedException){interruptedException.printStackTrace();}}}}}

     正确代码展示

/** 方案二、三公共代码* 预加载lua脚本*/private static DefaultRedisScript<Long> SECKILL_SCRIPT;static {SECKILL_SCRIPT = new DefaultRedisScript<>();// 这是第二种方案需要执行的lua脚本// SECKILL_SCRIPT.setLocation(new ClassPathResource("lua/seckill.lua"));// 这是第三种方案需要执行的lua脚本SECKILL_SCRIPT.setLocation(new ClassPathResource("lua/streamSeckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);}/*-----------------------------第三种方案: 使用Redis的stream消息队列 + redis + lua脚本判断秒杀资格添加消息队列 的方案-------------------------------------------------------------*/// 1,创建-- 秒杀线程池private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();//2. 初始化方法  一初始化就执行@PostConstructpublic void init(){SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}//3. 创建线程任务用于接收消息队列的信息private class VoucherOrderHandler implements Runnable{// 消息队列名称private String queueName = "stream.orders";@Overridepublic void run() {while (true) {try{//1. 获取队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.oredes >// 指定队列名称,组名称,消费者名称,读取模式,读取数量,阻塞时间,队列名称,读取位置List<MapRecord<String,Object,Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(queueName, ReadOffset.lastConsumed()));//2. 判断消息获取是否成功if( list == null || list.isEmpty()){//2.1 获取失败 说明没有消息 ---->继续循环continue;}// 解析消息中的订单信息MapRecord<String,Object,Object> record = list.get(0);//  获取键值对集合Map<Object,Object> values = record.getValue();// 获取订单信息VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);//3. 获取成功,执行订单创建handleVocherOrder(voucherOrder);//4. ACK确认  SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName,"g1",record.getId());}catch (Exception e) {// 消息没有被ACK确认 进入Pending Listlog.error("订单处理出现异常",e);handlePendingList();}}}// 5.取不到订单————— 处理Pending List中的订单信息private void handlePendingList(){while (true) {try {//1. 获取Pending List中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1  STREAMS stream.oredes 0// 指定队列名称,组名称,消费者名称,读取模式,读取数量,阻塞时间,队列名称,读取位置List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1),StreamOffset.create(queueName, ReadOffset.from("0")));//2. 判断消息获取是否成功if (list == null || list.isEmpty()) {//2.1 获取失败 说明Pending List没有消息 ---->结束循环break;}// 解析消息中的订单信息MapRecord<String, Object, Object> record = list.get(0);//  获取键值对集合Map<Object, Object> values = record.getValue();// 获取订单信息VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);//3. 获取成功,执行订单创建handleVocherOrder(voucherOrder);//4. ACK确认  SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName, "g1", record.getId());} catch (Exception e) {log.error("Pending List订单处理出现异常", e);try {Thread.sleep(20);}catch (InterruptedException interruptedException){interruptedException.printStackTrace();}}}}}// 4. 取到了订单—————创建订单private void handleVocherOrder(VoucherOrder voucherOrder){// 获取用户Long userId = voucherOrder.getUserId();// 1. 创建锁对象RLock lock =  redissonClient.getLock("lock:order:" + userId);//2. 尝试获取锁boolean isLock = lock.tryLock();// 3. 判断锁是否获取成功if(! isLock){log.error("不允许重复下单");}try {proxy.createVoucherOrder(voucherOrder);} finally {// 4. 释放锁lock.unlock();}}/***  秒杀优惠券下单------秒杀优化代码----lua脚本---主线程---使用Redis stream的消息队列完成的*/private IVoucherOrderService proxy;@Overridepublic Result seckillVoucher(Long voucherId) {// 获取用户Long userId = UserHolder.getUser().getId();// 获取订单idlong orderId =  redisIdWorker.nextId("order");//1.执行Lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString(),String.valueOf(orderId));//2.判断结果是否为0int r = result.intValue();if(r != 0){//3.不为0,代表没有购买资格return Result.fail(r == 1 ? "库存不足!" : "不能重复下单!");}//提前 获取代理对象proxy = (IVoucherOrderService) AopContext.currentProxy();//5.返回订单idreturn Result.ok(orderId);}/*** 秒杀优惠券下单------秒杀优化代码----创建订单* @param voucherOrder*/@Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {//4. 限制一人一单【悲观锁方案】Long userId = voucherOrder.getUserId();//4.1 查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();//4.2 判断订单是否存在// 是 -----> 返回异常信息---->结束if (count > 0) {log.error("用户已经购买了一次了");}//5. 扣减库存——解决超卖问题【乐观锁方案】boolean success = seckillVoucherService.update().setSql("stock = stock-1").eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0) // 库存大于0就行了.update();if (!success) {log.error("库存不足");}//6. 创建订单save(voucherOrder);}
}

   总结

        以前在遇到bug时,我总喜欢做的事是将别人写的代码复制回来。但是随着学习的深入发现,其实调代码是一件正常不过的事情,为此,锻炼自己发现问题、定位问题、解决问题能力十分重要,不断地刨根问底,才能愈发印象深刻。

相关文章:

【黑马点评】已解决java.lang.NullPointerException异常

Redis学习Day3——黑马点评项目工程开发-CSDN博客 问题发现及描述 在黑马点评项目中&#xff0c;进行到使用Redis提供的Stream消息队列优化异步秒杀问题时&#xff0c;我在进行jmeter测试时遇到了重大的错误 发现无论怎么测试&#xff0c;一定会进入到catch中&#xff0c;又由…...

计算机专业的就业方向

计算机专业的就业方向 亲爱的新生们&#xff0c;欢迎你们踏上计算机科学的旅程&#xff01;作为一名计算机专业的学生&#xff0c;你们即将进入一个充满无限可能的领域。今天&#xff0c;我将为大家介绍计算机专业的一些主要就业方向&#xff0c;帮助你们了解未来的职业选择。…...

VSCode C++ Tasks.json中的变量

前言 上文介绍了在VSCode中创建C项目和编译多文件的情况。本文将介绍Tasks.json中一些变量的含义&#xff1b; 内容 tasks.json文件 下文参考VSCode文档&#xff1a;Visual Studio Code 变量参考 预定义标量 ${userHome} - 用户主文件夹的路径${workspaceFolder} - 在 VS Co…...

第一次安装Pytorch

1、新版本的Anaconda内置的python版本是3.12&#xff0c; 目前 Windows 上的 PyTorch 仅支持 Python 3.8-3.11;不支持 Python 2.x。 1、创建运行环境 在不创建虚拟环境的情况下&#xff0c;不建议使用最新的Python和Anaconda。 在几次失败后&#xff0c;我使用的是Anaconda3-2…...

Python数据分析-Steam 收入排名前 1500 的游戏

一、研究背景 随着全球数字化进程的加速&#xff0c;电子游戏产业已成为全球娱乐产业的重要组成部分&#xff0c;吸引了越来越多的资本与消费者关注。特别是基于互联网的游戏平台&#xff0c;如Steam&#xff0c;已成为全球范围内发行和销售游戏的重要渠道。Steam平台不仅为玩…...

Android14请求动态申请存储权限

Android14请求动态申请存储权限 Android14和Android15存储权限有增加多了选择部分&#xff0c;还是全部。一个小小的存储权限真的被它玩出了花来。本来Android13就将存储权限进行了3个细分&#xff0c;是图片&#xff0c;音频还是视频文件。 步骤一&#xff1a;AndroidManife…...

Doris:数据库建表最佳实践

目录 一、表模型推荐归约 二、字段推荐归约 三、建表推荐归约 四、建表强制归约 五、最佳实践 Doris 数据表模型上目前分为三类&#xff1a;DUPLICATE KEY, UNIQUE KEY, AGGREGATE KEY。因为数据模型在建表时就已经确定&#xff0c;且无法修改。所以&#xff0c;选择一个合…...

Parallels Desktop 20(Mac虚拟机) v20.0.0 for Mac 最新破解版(支持M系列)

Parallels Desktop 20 for Mac 正式发布&#xff0c;完全支持 macOS Sequoia 和 Windows 11 24H2&#xff0c;并且在企业版中引入了全新的管理门户。 据介绍&#xff0c;新版本针对 Windows、macOS 和 Linux 虚拟机进行了大量更新&#xff0c;最大的亮点是全新推出的 Parallels…...

【已解决】华为AR100-S路由器 恢复出厂后,找不到5G wifi的设置

前两帖讨论了华为AR100-S路由器&#xff1a; 一是用电脑浏览器访问web管理界面报错的解决&#xff0c;详情点这里&#xff01; https://blog.csdn.net/weixin_62598385/article/details/142215136 再就是如何回复出厂&#xff0c;也即如何复位&#xff0c; 详情点这里&#xff…...

【MongoDB】--MongoDB批量操作

目录 一、批量更新 一、批量更新 /*** 批量更新的操作* return*/public int batchUpdate(){List<StudentDo> list new ArrayList<>(); //要修改的一批数据List<Pair<Query, Update>> updateList new ArrayList<>(list.size());BulkOperations …...

数据库常规操作

常用的 SQL 语法和操作&#xff1a; 数据定义语言&#xff08;DDL&#xff09; 1.创建数据库CREATE DATABASE database_name;2.删除数据库DROP DATABASE database_name;3.创建表CREATE TABLE table_name (column1 datatype constraints,column2 datatype constraints,...);4.删…...

基于STM32设计的水渠闸门远程控制系统(华为云IOT)(226)

文章目录 一、前言1.1 项目介绍【1】开发背景【2】项目实现的功能【3】项目硬件模块组成【4】ESP8266工作模式配置1.2 设计思路【1】整体设计思路【2】整体构架【3】上位机开发思路1.3 项目开发背景【1】选题的意义【2】可行性分析【3】参考文献【4】摘要1.4 开发工具的选择【1…...

鸿蒙开发(NEXT/API 12)【响应校验】远场通信服务

本协议栈框架支持校验响应功能&#xff0c;应用添加了响应校验器后&#xff0c;可在ResponseValidationCallback中判断响应是否符合预期&#xff0c;不符合那么框架会抛异常。 开发步骤 导包。 import { rcp } from kit.RemoteCommunicationKit;添加响应校验器并且发起请求。 …...

2024最新!!!iOS高级面试题,全!(二)

iOS应用是如何启动以及如何优化 pre-main阶段 加载动态链接器dyld到App进程 加载动态库&#xff08;包括所依赖的所有动态库&#xff09; Rebase 修正内部的指针指向 Bind 修正外部指针指向 初始化Objective C Runtime 包括oc的类、分类的注册&#xff0c;selector唯一性检查等…...

【C#生态园】构建你的C#操作系统:框架选择与实践

探秘C#操作系统开发框架&#xff1a;从框架选择到实际应用 前言 在当今信息技术高度发达的时代&#xff0c;操作系统开发框架为软件工程师提供了全新的可能性。本文将介绍一系列用于C#的操作系统开发框架&#xff0c;探讨它们的核心功能、使用场景、安装与配置方法以及API概览…...

ADB 安装教程:如何在 Windows、macOS 和 Linux 上安装 Android Debug Bridge

目录 一、ADB 介绍 二、Windows 系统安装 ADB 1. 下载 ADB 2. 解压文件 3. 验证 ADB 安装 4. 配置环境变量 5. 验证全局 ADB 使用 三、macOS 系统安装 ADB 1. 下载 ADB 2. 解压文件 3. 配置环境变量 4. 验证 ADB 安装 四、Linux 系统安装 ADB 1. 使用包管理器安装…...

java(2)方法的使用

目录 1.前言 2.正文 2.1方法的定义 2.2方法的调用过程 2.3方法的实参与形参 2.3.1形参 2.3.2实参 2.3.3参数传递 2.4方法的重载 3.小结 1.前言 哈喽大家好啊&#xff0c;今天博主继续带领大家学习java的基本语法&#xff0c;java的基础语法部分打算用六到七篇博文完…...

基于对数变换的图像美白增强,Matlab实现

博主简介&#xff1a;matlab图像处理&#xff08;QQ:3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于对数变换的图像美白增强&#xff0c;用matlab实现。 一、案例背景和算法介绍 这次案例是美白算法&…...

MySQL高阶1873-计算特殊奖金

目录 题目 准备数据 分析数据 总结 题目 编写解决方案&#xff0c;计算每个雇员的奖金。如果一个雇员的 id 是 奇数 并且他的名字不是以 M 开头&#xff0c;那么他的奖金是他工资的 100% &#xff0c;否则奖金为 0 。 返回的结果按照 employee_id 排序。 准备数据 Crea…...

Ngnix 在windows上的简单使用

安装 下载链接: nginx: download 选择页面中 Stable version 下的windows版本直接下载解压到本地。 运行nginx: 解压到本地后,结构如图: cmd 进入到上图的根目录,运行 start nginx ,即可开启。 打开 http://localhost 进行查看,如果正常打开nginx的测试页面,则说…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...