news 2026/9/23 12:43:36

Java项目启动报NullPointer,5个最佳实践救你命

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java项目启动报NullPointer,5个最佳实践救你命

Java项目启动报NullPointer,5个最佳实践救你命

刚接手的Java老项目,复制了同事的启动代码,结果控制台一片红,满屏NullPointerException。你盯着报错信息看了十分钟,连错在哪一行都不知道,更别提怎么改了。这种“代码能跑在我机器上,换个环境就崩”的噩梦,每个Java开发者都经历过。

别慌。今天不讲深奥理论,只聊那些让你抓狂的报错背后,到底藏着什么坑。我会把最常见的几个坑拆开揉碎,告诉你为什么错、怎么改、以后怎么防。这些经验,是从无数个通宵调试里攒出来的,希望能帮你少走点弯路。

坑一:依赖冲突导致类加载失败

现象:项目能编译通过,但一启动就报ClassNotFoundExceptionNoClassDefFoundError。明明pom.xml里写了依赖,为什么找不到类?

根本原因:Maven依赖传递机制。A项目依赖B,B依赖C,但A又直接依赖了C的另一个版本。Maven默认选择“最近优先”原则,可能导致加载了不兼容的版本。比如Spring Boot 2.x和3.x的包路径都变了,混用直接崩。

错误写法

<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.20</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>6.0.1</version>
</dependency>

这段代码看似没问题,但Spring 6.0.1要求Java 17+,而Spring 5.3.20基于Java 8设计。混用会导致方法签名不匹配,运行时直接抛异常。

正确写法

<dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>3.1.0</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>

用Spring Boot的BOM统一管理版本。所有Spring组件版本由BOM锁定,避免手动指定版本造成冲突。这是Spring官方源码仓库里明确推荐的依赖管理方式,比手动对齐版本可靠得多。

复现与修复

  1. 运行mvn dependency:tree查看依赖树
  2. 找到冲突的库,用-Dverbose参数查看排除信息
  3. 在冲突依赖上添加<exclusions>标签排除旧版本
  4. 重新打包测试

规避建议:新项目统一用Spring Boot Starter,老项目迁移时先跑dependency:tree检查。别相信IDE的自动解析,Maven的依赖仲裁规则比想象中复杂。

坑二:时区处理导致数据错位

现象:用户在北京下单,数据库里存的时间比实际晚8小时。或者反过来,比实际早8小时。客服打电话来投诉,你查日志发现时间对不上。

根本原因:JVM默认时区是服务器所在时区,而数据库连接可能用了不同时区。java.util.Date不存储时区信息,只存时间戳,转换时依赖JVM默认时区,跨环境部署必然出错。

错误写法

Date now = new Date();
String sql = "INSERT INTO orders (created_at) VALUES (" + now + ")";

直接拼接Date对象,JVM会调用toString()方法,格式不固定,且依赖默认时区。在UTC服务器上跑,存进去的就是UTC时间,北京时间用户看到的就是错乱数据。

正确写法

ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
String sql = "INSERT INTO orders (created_at) VALUES (?)";
preparedStatement.setObject(1, now.toLocalDateTime());

明确指定时区,用LocalDateTime存入数据库(假设数据库列是DATETIME类型),彻底摆脱JVM默认时区的干扰。这是Java 8时间API的设计初衷,也是Spring官方文档里反复强调的最佳实践。

复现与修复

  1. 检查数据库列类型,确认是TIMESTAMP还是DATETIME
  2. TIMESTAMP会按时区转换,DATETIME不会,选错直接踩坑
  3. 在连接串里显式指定时区:jdbc:mysql://host:3306/db?serverTimezone=Asia/Shanghai
  4. 统一所有环境用同一时区配置,别靠JVM默认值

规避建议:新项目一律用LocalDateTime+ZonedDateTime,别再用Date。如果必须用Date,永远手动指定时区。数据库设计时,DATETIME存业务时间,TIMESTAMP存系统时间,分开管理。

坑三:线程池参数配置不当导致OOM

现象:系统运行几天后内存飙升,最终OOM。看堆栈全是Thread对象,但业务逻辑里没看到哪里创建了线程。

根本原因:每次请求都new Thread(),或者用了Executors.newFixedThreadPool()这类便捷方法。这些方法创建的线程池队列是无界的(LinkedBlockingQueue默认容量Integer.MAX_VALUE),任务堆积直接撑爆内存。

错误写法

ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(() -> {// 业务逻辑
});

看起来简洁,但队列无界。如果任务处理速度跟不上提交速度,队列会无限增长,每个任务对象都占内存,最终OOM。

正确写法

ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 20,60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build(),new ThreadPoolExecutor.CallerRunsPolicy()
);

明确指定核心线程数、最大线程数、队列容量、拒绝策略。队列有界,超过容量时触发拒绝策略,而不是无限堆积。这是阿里巴巴Java开发手册里强制要求的写法,也是生产环境避免OOM的基本功。

复现与修复

  1. 用JVisualVM或Arthas监控线程数变化
  2. 发现线程数持续增长,定位到创建线程的代码
  3. 替换为有界队列的ThreadPoolExecutor
  4. 设置合理的拒绝策略,CallerRunsPolicy让调用线程自己执行,起到背压作用

规避建议:永远别用Executors的便捷方法创建线程池。每个业务场景单独配置线程池,别共用。线程池参数不是拍脑袋定的,要压测后调整。核心线程数参考CPU核数,队列容量参考业务峰值。

坑四:SQL注入与慢查询隐患

现象:系统突然变慢,数据库CPU 100%,连接池耗尽。查慢查询日志,发现一堆全表扫描的SQL。

根本原因:字符串拼接SQL,用户输入直接进SQL语句。要么被注入,要么走了全表扫描。LIKE '%keyword%'这种写法,索引直接失效。

错误写法

String sql = "SELECT * FROM users WHERE name LIKE '%" + keyword + "%'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);

字符串拼接,keyword如果是'; DROP TABLE users; --,直接删库。就算没注入,LIKE '%keyword%'前缀模糊查询,B+树索引无法使用,全表扫描。

正确写法

String sql = "SELECT * FROM users WHERE name LIKE ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1, "%" + keyword + "%");
ResultSet rs = ps.executeQuery();

参数化查询防注入。但LIKE '%keyword%'还是慢,要加全文索引或用搜索引擎。如果必须前缀模糊,改成LIKE 'keyword%',索引就能用上。

复现与修复

  1. 开启MySQL慢查询日志,long_query_time=1
  2. EXPLAIN分析SQL执行计划,看type列是不是ALL
  3. 优化SQL,能走索引就走索引
  4. 高频模糊查询场景,考虑Elasticsearch

规避建议:所有SQL用PreparedStatement,别用字符串拼接。LIKE查询谨慎使用,优先精确匹配或前缀匹配。复杂查询场景,评估是否需要搜索引擎。数据库索引不是越多越好,要结合实际查询模式设计。

坑五:异常吞掉导致问题难追踪

现象:系统报错日志里全是Exception in thread "main" java.lang.Exception,没堆栈,没上下文,不知道哪里出的问题。

根本原因catch(Exception e) { e.printStackTrace(); }或者catch(Exception e) { }。前者打印到控制台,生产环境控制台没人看;后者直接吞掉,连日志都没有。异常没带上下文,排查时只能靠猜。

错误写法

try {processOrder(orderId);
} catch (Exception e) {e.printStackTrace();
}

printStackTrace输出到System.err,生产环境通常重定向到/dev/null,等于没打。就算能看到,也没说哪个订单、哪个环节出错,排查时抓瞎。

正确写法

try {processOrder(orderId);
} catch (Exception e) {log.error("处理订单失败, orderId={}, 原因={}", orderId, e.getMessage(), e);throw new BusinessException("订单处理异常", e);
}

用SLF4J日志框架,记录关键业务参数(orderId),带完整堆栈(第三个参数传e)。业务层捕获后包装成业务异常,向上传递,由统一异常处理器记录并返回友好提示。这是Spring Boot异常处理的标准做法,也是生产环境可观测性的基础。

复现与修复

  1. 全局搜索e.printStackTrace()和空catch
  2. 替换为SLF4J日志,带上业务关键参数
  3. 配置logback,生产环境INFO级别,异常ERROR级别
  4. 接入ELK或云日志服务,方便检索和分析

规避建议:禁止空catch块,代码审查时重点检查。日志要带上下文,没上下文的日志等于没打。异常处理要分层,底层记详细,上层给友好提示。别怕日志量大,排查问题时,少一行日志可能多查两小时。

这几个坑,覆盖了依赖管理、时间处理、线程池、SQL、异常处理五个高频场景。每个坑背后,都是无数个生产事故的教训。Java生态成熟,但坑也不少,关键在于理解底层机制,别靠记忆和运气写代码。

你更常用哪种写法?评论区交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 12:43:17

分区魔术师win7性能优化

3个Win7分区魔术师实战项目避坑指南 看了一堆教程还是不会写项目?别慌,这太正常了。 我在CSDN上见过太多人问“分区魔术师Win7怎么调整大小失败”,底下全是“重启试试”、“重装系统”这种废话。 真正的痛点在于:你照抄了代码或步骤,一跑就崩,或者改了个地方全盘变砖。…

作者头像 李华
网站建设 2026/9/23 12:43:12

Ret2Libc漏洞利用技术详解与实战

1. 漏洞利用技术背景解析Ret2Libc&#xff08;Return to Libc&#xff09;是一种经典的二进制漏洞利用技术&#xff0c;主要应用于现代操作系统针对栈溢出漏洞的防护机制&#xff08;如NX/DEP&#xff09;被启用时的攻击场景。当程序启用了NX&#xff08;No-eXecute&#xff09…

作者头像 李华
网站建设 2026/9/23 12:43:10

3天搞定改签规则引擎 保姆级教程解决代码跑不通痛点

3天搞定改签规则引擎 保姆级教程解决代码跑不通痛点 刚把同事发来的改签规则代码复制进项目,结果控制台直接炸出 TypeError ,看着满屏报错却不知从何下手,这种抓狂感我太懂了。很多开发者习惯直接拷贝网上的片段,忽略了上下文依赖和版本差异,导致看似简单的逻辑一跑就崩。别慌,今天这篇保姆级教程不整虚…

作者头像 李华
网站建设 2026/9/23 12:42:59

CodeGuide 手写 ORM 框架实现:从 JDBC 到 MyBatis 风格中间件(第 7 章实战)

文档教程后端 【免费下载链接】CodeGuide :books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总&#xff0c;旨在为大家提供一个清晰详细的学习教程&#xff0c;侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助&#xff0c;请给予支持(关注、…

作者头像 李华
网站建设 2026/9/23 12:42:51

2026最新李子柒的微博API踩坑实录:版本升级后API全变了的自救指南

2026最新李子柒的微博API踩坑实录:版本升级后API全变了的自救指南 版本升级后 API 全变了,这大概是后端开发者在 2026 最新技术栈里最崩溃的瞬间。你信誓旦旦地写着调用 liziqi.weibo.status.get() ,结果部署上去,控制台直接吐出一长串 404 Not Found…

作者头像 李华
网站建设 2026/9/23 12:42:47

3种CSS透明度图解原理,配置环境不卡壳

3种CSS透明度图解原理,配置环境不卡壳 配置环境就卡半天,是不是常为了一个半透明效果,在 opacity 和 rgba 之间反复横跳?别急,这不只是写代码慢的问题,而是没搞懂 图解原理…

作者头像 李华