当前位置: 首页 > 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的测试页面,则说…...

Windows10下V-REP教育版安装保姆级教程(附百度网盘资源与避坑点)

Windows10系统V-REP教育版完整安装指南&#xff1a;从下载到实战避坑在机器人仿真和自动化控制领域&#xff0c;V-REP&#xff08;现更名为CoppeliaSim&#xff09;作为一款功能强大的跨平台机器人仿真软件&#xff0c;已经成为众多工科学生和研究人员的首选工具。特别是其教育…...

CentOS服务器上VNC连接失败?手把手教你排查并修复个人端口问题(附重启命令)

CentOS服务器VNC连接故障深度排查指南&#xff1a;从原理到实战当你在深夜赶项目时&#xff0c;突然发现VNC连接不上服务器&#xff0c;那种焦虑感我深有体会。去年参与半导体器件仿真项目时&#xff0c;我也曾被这个问题困扰整整两天。本文将分享一套经过实战检验的排查方法论…...

taotoken如何帮助ubuntu开发者应对大模型api的频繁更新与版本迭代

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 Taotoken如何帮助Ubuntu开发者应对大模型API的频繁更新与版本迭代 对于在Ubuntu环境下进行开发的工程师而言&#xff0c;大模型API…...

告别混乱绑定!在UE5 GAS中优雅管理技能输入(基于GameplayTag)

告别混乱绑定&#xff01;在UE5 GAS中优雅管理技能输入&#xff08;基于GameplayTag&#xff09;当你的UE5 RPG项目发展到中期&#xff0c;技能数量从十几个膨胀到几十个时&#xff0c;最痛苦的莫过于发现InputAction绑定已经变成一团乱麻。每次新增技能都要修改输入绑定逻辑&a…...

2026 文章代码高亮方案选型

将基于 Prism.js 或 Highlight.js 的传统高亮方案与基于 Shiki 的现代化高亮方案进行对比&#xff0c;其核心区别在于底层解析原理的不同&#xff08;正则表达式 vs. TextMate 语法树&#xff09;。 以下是两种方案的底层原理、各自优缺点、核心对比矩阵以及适用场景的详细分析…...

基于CNN的食双星光变曲线自动化参数初估模型EBOP MAVEN

1. 项目概述与核心价值在恒星天体物理领域&#xff0c;食双星系统一直扮演着“宇宙实验室”的关键角色。通过分析两颗恒星相互绕转时周期性相互遮挡产生的光变曲线&#xff0c;我们可以像解谜一样&#xff0c;精确反演出恒星的质量、半径、轨道倾角等基本物理参数。这些参数是构…...

前馈补偿技术:用数字预失真驯服放大器非线性失真

1. 项目概述&#xff1a;用前馈补偿驯服放大器失真在音频发烧友和硬件工程师的圈子里&#xff0c;追求“高保真”几乎是一种信仰。我们总希望从扬声器里传出的声音&#xff0c;是录音现场或音乐制作人意图的完美复刻&#xff0c;纤毫毕现&#xff0c;不带一丝杂质。然而&#x…...

终极Windows风扇控制指南:FanControl让你的电脑安静又高效

终极Windows风扇控制指南&#xff1a;FanControl让你的电脑安静又高效 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trendin…...

AI专著生成必备工具,轻松撰写20万字专著,质量与效率双保障!

学术专著的写作是一个严谨的过程&#xff0c;其背后需要大量的资料和数据作为基础。搜集和整理这些资料与数据往往是写作过程中最繁琐且耗时的部分。研究人员需要广泛收集国内外的前沿文献&#xff0c;确保所用文献不仅具备权威性&#xff0c;还要与研究主题密切相关。同时&…...

C++的单例模式及其作用

什么是单例模式&#xff1f;无论是在面向对象编程还是软件架构中&#xff0c;单例模式都扮演着至关重要的角色。它不仅能够确保一个类只有一个实例存在&#xff0c;还能够提供全局访问点&#xff0c;使得我们可以方便地在程序的任何地方使用该实例。但有几个设计模式并非解决抽…...