Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >三歪写Bug写哭了

三歪写Bug写哭了

作者头像
Java3y
发布于 2020-06-01 06:12:07
发布于 2020-06-01 06:12:07
56800
代码可运行
举报
文章被收录于专栏:Java3yJava3y
运行总次数:0
代码可运行

三歪最近每天都很忙,一直在整合系统,期间遇到了不少奇奇怪怪的问题。今天想分享一个Bug,如果后面你遇到了希望你能想起这篇文章。

Bug不会无缘无故出现,修复一个又一个Bug往往不是凭借着运气。?

看到我这篇文章的读者,其中也不乏多年开发经验的老兵,如果你有一些不错的修Bug技巧或者心得或者工具,希望可以在评论区不吝分享?

背景

一个系统会有多个工程,一个工程会有多个依赖,工程与工程之间的依赖很容易就冲突。

如果有做过系统整合的同学会发现,每天可能会花很长的时间排除依赖

举个很简单的例子:现在工程上有两个依赖,但是两个依赖的版本不一样。在编译的时候没有任何问题,但是启动的时候就会有各种稀奇古怪的错误。

这些稀奇古怪的错误往往不能指引你去立马发现这是依赖冲突导致的问题,但是有经验的人会想到:「这绝壁是依赖冲突了,我的代码不可能有问题!!」?

在某年某月某日,我系统已经启动起来了,在上线之前肯定要自测一下各个功能有无问题。测试到短信下发渠道的时候,发现出错了。

众所周知,下发一条短信是需要知道用户的手机号的。如果业务方传给我的是一个站内的userId,那我需要将userId转成手机号

顺便来简单科普一下这里的技术设计:

  • 如果用户绑定了手机号,那么在绑定后会触发一条topic消息。我这边会监听到这条消息,然后将这条消息清洗/处理,完了之后写到引擎中。
  • 提供一个对外暴露引擎Client客户端使用(传入userId,查到userId对应的手机号)

对外暴露api层给业务方使用(封装了引擎的客户端)

我们想要对Id进行转换,那么就引入searchClient的依赖,然后调他封装好的方法,就OK了。

分享遇到的Bug

于是我把searchClient引入到项目里边,项目正常启动起来。我在验证功能是否正常的时候,重复报了一个错误:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java.lang.NoClassDefFoundError: Could not initialize class xxx

一次印象:我见到这个错误的时候,会觉得:我的依赖包没引入进去吧?怎么没找到呢

于是我就去确认我的包到底有没有引入进去,经过一大轮验证,我的依赖包是的的确确是引入了进去啊。那怎么会报NoClassDefFoundError的呢。

于是我就怀疑,是不是我引入的依赖包跟原有的工程依赖冲突呢?于是我就去对比版本,也发现好像没什么问题啊。searchClient依赖的版本都一样的,Zookeeper的版本也是一样的,怎么还是报NoClassDefFoundError呢。

检查maven版本,我一般是先用maven的插件”Maven Helper“在当前的工程下去看看有没有类似的包冲突了,如果有类似的包冲突了那直接在插件上Exclude就好了。

如果发现Maven Helper 不好使,我就会用mvn dependency:tree去看看项目里有没有版本不一样的依赖,效果如下:

但走到一步,貌似没找到相关的依赖有啥问题(毕竟也不能瞎排依赖)。于是就只能去DeBug看源码,看源码的时候发现出错是在创建单例对象的时候。

代码里边用了静态内部类的方式去创建对象,一直报的错就是没找到这个内部类。那也挺符合NoClassDefFoundError这个报错信息的。

于是我就去Google也搜了一下,搜到了相关的博客:

仔细一看,发现说得真的很有道理。跟我的错误长得一模一样,我终于找到了......

这个博主好像也没怎么说解决啊?那怎么把内部类的class文件加载到对应的目录呢??折腾了半天,把依赖包的版本也升级了一把,貌似也没啥用啊。。

于是我陷入了沉思,这就为啥报NoClassDefFoundError呢,不对劲啊,我依赖明明是有的,依赖包好像也没冲突啊。

如果你直接以NoClassDefFoundError去搜是很难搜到你想要的信息,但是以NoClassDefFoundError 静态内部类的关键字去搜,检索出来的信息就不一样了。

我就继续Google,又学习到了另一个问题:单例对象以静态内部类的方式实现,如果在创建对象的时候出了问题,那么报的就是NoClassDefFoundError

比如以下的代码(分别在main方法获取了三次单例对象,但是在创建对象的时候失败了):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:Java3y
 */
public class SingleTon {
    private SingleTon() {
        int i = 1 / 0;
    }

    private static class Holder {
        private static final SingleTon INSTANCE = new SingleTon();
    }

    public static SingleTon getInstance() {
        return Holder.INSTANCE;
    }

    public static void main(String[] args) {
        try {
            System.out.println("First");
            SingleTon.getInstance();
        } catch (Throwable t) {
            t.printStackTrace();
        }

        try {
            System.out.println("Second");
            SingleTon.getInstance();
        } catch (Throwable t) {
            t.printStackTrace();
        }
        try {
            System.out.println("Three");
            SingleTon.getInstance();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

报错信息:

看到这里,我在怀疑:是不是我没有好好看第一次的调用失败信息,其实在创建单例的时候内部有出错了呢。然后在心里边也有个问号:这别人提供好的二方包依赖,怎么可能这么随意就失败了,不可能吧。

于是我就重启了一把,然后顺便Debug进去看看相关的逻辑。进去创建单例对象时,走到某条分支的时候,发现有连接ZK的 curator包有两个依赖可供我选择。那我知道了,绝壁又是依赖冲突了

学习笔记:

类加载时静态变量只会在第一次加载时,进行初始化,此后不管成不成功,都不会进行第二次初始化了。

这个Bug在最开始的时候已经想过是不是依赖冲突的问题,但是我们怀疑版本依赖往往只会在顶层的jar包上怀疑,至于内部的jar包冲突一般也不好发现,发现了也不敢去乱排(毕竟复现的报错不是包依赖的问题啊!!)。

排Bug经验反思:

看错误信息固然很重要,但不要忽略第一次的报错信息(像整合工程这样的操作,绝大多数时候都是依赖冲突的问题。如果报错的信息比较诡异,可以重启直接Debug相应的位置上看看究竟是什么原因

参考资料:

  • https://www.jianshu.com/p/f48c90270fae

Tomcat打印大量的debug日志

整合了个系统以后,发现tomcat_stdout.log这个文件疯狂打印debug日志,都快把三歪打哭了。

于是我就一顿分析:项目里边用的是logback去打印日志的,那么我控制一下logback的日志级别应该就行了吧。但是我没找到tomcat_stdout.log这个日志文件的级别设置。

于是我就根据关键字搜:tomcat_stdout.log DEBUG日志太多。搜索了一轮,感觉都没啥卵用。虽然提供了很多解决方案(但这些我都看不懂,我也不怀疑是服务器上的配置存在问题。)

搜了一轮logback 设置日志级别logback debug 日志太多这些关键字都没有用,隐隐约约能发现是因为logback和log4j冲突了。

最后我搜了一下日志打出的debug信息,以ClientCnxn debug关键字去搜索就才搜到相关的解决方案。

发现还是包依赖冲突的问题,把Zookeeper的log4j的包排掉,就解决了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
  <groupId>org.apache.zookeeper</groupId>
  <artifactId>zookeeper</artifactId>
  <version>3.4.6</version>
  <exclusions>
    <exclusion>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
    </exclusion>
    <exclusion>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
    </exclusion>
  </exclusions>
</dependency>

排Bug经验反思:

上面的表面现象的确是Tomcat debug日志量太大了,而仅仅用这个关键字(tomcat_stdout.log DEBUG)去搜索引擎搜索很多时候都不是自己想要的解决方法。用logback 设置日志级别logback debug日志量等关键字去搜索也只能搜出logback的相关教程。

透过表面现象,把输出的Debug日志加上关键字去搜,往往就能得出想要的结果。

参考资料:

  • https://blog.csdn.net/qq_32465815/article/details/82081300
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java3y 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Slf4j 包老冲突,每次排查半天,是什么原因?怎么解决?
在进行 Java 开发时,通常我们会选择 Slf4j 作为日志门面,但日志实现却不尽相同。如果系统运行中同时存在多个日志实现,就会出现类似下图的 Warning。
芋道源码
2021/01/25
2.8K0
Slf4j 包老冲突,每次排查半天,是什么原因?怎么解决?
看 Log4j2 频繁爆雷给出几点日志使用建议
使用 Log Facade 可以⽅方便便的切换具体的日志实现。⽽且,如果依赖多个项目,使⽤了不同的 Log Facade,还可以⽅方便便的通过 Adapter 转接到同一个实现上。如果依赖项目直接使用了多个不同的日志实现,会非常糟糕。
IT技术小咖
2021/12/20
7090
看 Log4j2 频繁爆雷给出几点日志使用建议
Spring Boot 日志框架 整体介绍 及 使用
SpringBoot 默认继承了日志框架,如果需要更改日志框架,需要首先把原来的日志框架屏蔽后,再导入新的日志框架。 SpringBoot 的默认日志框架为 SLF4j 和 Logback,SLF4J作为日志接口层,Logback作为日志的实现层,
Freedom123
2024/03/29
1790
Java日志体系框架总结:JUL、JCL、SLF4J、Log4j、Logback、Log4j2
日志记录是应用程序运行中必不可少的一部分。具有良好格式和完备信息的日志,可以在程序出现问题时帮助开发人员迅速地定位错误的根源。日志所能提供的功能是多种多样的,包括记录程序运行时产生的错误信息、状态信息、调试信息和执行时间信息等。
johnny666
2024/09/24
4170
全面梳理 Spring Boot 日志体系
之前录过一个视频和大家分享 Spring Boot 日志问题,但是总感觉差点意思,因此松哥打算再通过一篇文章来和大家捋一捋 Java 中的日志问题,顺便我们把 Spring Boot 中的日志问题也说清楚。 1. Java 日志概览 说到 Java 日志,很多初学者可能都比较懵,因为这里涉及到太多东西了:Apache Commons Logging、Slf4j、Log4j、Log4j2、Logback、Java Util Logging 等等,这些框架各自有什么作用?他们之间有什么区别? 1.1 总体概览
江南一点雨
2022/05/12
5550
全面梳理 Spring Boot 日志体系
Spring日志管理
本博客基于SpringBoot_1.3.6 大家请先简单看下这篇英文的官方文档,文中有说 SpringBoot 内部日志系统使用的是 Commons Logging 并且 SpringBoot 给 JDKLogging , Log4j2(Log4j也是支持的) , Logback 都提供了默认配置,并且如果使用了 Starters ,那么默认使用 Logback 。 那么什么是这个这个 Starters 呢?大家请看我的pom文件:
全栈程序员站长
2022/08/30
1.4K0
Spring日志管理
Springboot整合log4j2(按级别拆分)
创建log4j2.xml文件,放在工程resources目录里。这样就可以不加任何配置。如果你需要指定配置文件需要在Spring boot 配置文件application.yml中指定 logging.config 属性。
鱼找水需要时间
2023/02/16
7560
Springboot整合log4j2(按级别拆分)
『互联网架构』软件架构-Spring boot集成日志框架(89)
在spring-boot-starter 依赖中,添加了 spring-boot-starter-logging依赖
IT架构圈
2019/06/25
4280
『互联网架构』软件架构-Spring boot集成日志框架(89)
springboot06、log4j2日志配置
springboot06、log4j2日志配置 目录 前言: 1、pom配置 2、log4j2-spring.xml配置文件 3、在application.properties中引入log4j2的配置 4、log4j使用 ---- 前言: 日志接口(slf4j) slf4j是对所有日志框架制定的一种规范、标准、接口,并不是一个框架的具体的实现,因为接口并不能独立使用,需要和具体的日志框架实现配合使用(如log4j、logback)。 接口用于定制规范,可以有多个实现,使用时是面向接口的(导入的包
红目香薰
2022/11/30
3990
springboot06、log4j2日志配置
Spring Cloud 2.x系列之springcloud整合logback打印sql语句
Logback是由log4j创始人设计的又一个开源日志组件。logback当前分成三个模块:logback-core、logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个 改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。 Logback是要与SLF4J结合起来用的。
BUG弄潮儿
2022/06/30
8020
Spring Cloud 2.x系列之springcloud整合logback打印sql语句
13.4 库依赖冲突问题:Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path13.4 库依
引入第三方库'org.raml:raml-parser:0.8.12',导致slf4j依赖冲突。
一个会写诗的程序员
2018/08/20
5.9K0
13.4 库依赖冲突问题:Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path13.4 库依
SpringBoot之SpringBoot整合log4j
在默认的情况下,SpringBoot自动整合了最简单的logback,那么要想使用log4j就要先排除默认的日志框架,然后添加log4j场景启动器
彼岸舞
2021/02/01
5.9K0
SpringBoot之SpringBoot整合log4j
SpringBoot启动报错:LoggerFactory is not a Logback LoggerContext but Logback is on the classpath
原文链接:https://blog.csdn.net/zhanggonglalala/article/details/88953345
chenchenchen
2019/09/02
64.8K1
SpringBoot启动报错:LoggerFactory is not a Logback LoggerContext but Logback is on the classpath
Spring boot集成日志框架
在spring-boot-starter 依赖中,添加了 spring-boot-starter-logging依赖
IT架构圈
2021/10/21
1.8K0
Spring boot集成日志框架
JAVA日志框架适配/冲突解决方案
上面的这些问题,基本都是由于多套日志框架共存或配置错误导致的。那么为什么会出现共存或者冲突呢? 一般是以下几种原因:
神秘的寇先森
2021/04/13
1.6K0
《Springboot极简教程》问题解决:Idea解决Gradle库依赖冲突问题:Detected both log4j-over-slf4j.jar AND slf4j-log4j12.jar on
参考: https://docs.gradle.org/current/userguide/userguide_single.html#sec:listing_dependencies
一个会写诗的程序员
2018/08/20
9960
《Springboot极简教程》问题解决:Idea解决Gradle库依赖冲突问题:Detected both log4j-over-slf4j.jar AND slf4j-log4j12.jar on
解决java.lang.IllegalStateException: Detected both log4j-over-slf4j.jar AND bound
在使用Java编程的过程中,我们常常会遇到各种各样的错误和异常。其中一个常见的问题是在依赖库中出现了相互冲突的情况,比如在使用日志框架时可能会出现​​java.lang.IllegalStateException: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path​​的异常。这个异常是由于在项目的依赖中同时存在了​​log4j-over-slf4j.jar​​和​​slf4j-log4j12.jar​​这两个不兼容的库引起的。
大盘鸡拌面
2023/11/03
7660
教你全方位解决Java 日志框架冲突!
你是否遇到过配置了日志,但打印不出来的情况?你是否遇到过配置了 logback,启动时却提示 log4j 错误的情况?像下面这样:
二哥聊运营工具
2022/07/11
1K0
教你全方位解决Java 日志框架冲突!
SpringBoot 笔记 ( 三 ):日志系统
SpringBoot 笔记 ( 三 ):日志系统 1、日志框架 日志框架就是防止我们再去像以前那样,一直进行System.out.println(“”)将关键数据打印在控制台。框架来记录系统的一些运行时信息,但是随着日志框架的增长,和接口的不一致,导致了使用上的差别很大,​这里采用了一个类似于数据库驱动的模式,数据库驱动是 Java 提供的一个 API,然后真正的实现是需要各个数据库厂商去完成的,而 log 也开始采用这种面向接口编程的方法采用日志抽象层。 市面上的日志框架 JUL、JCL、Jboss-l
lwen
2018/04/16
2.1K0
从源码来理解slf4j的绑定,以及logback对配置文件的加载
  项目中的日志系统使用的是slf4j + logback。slf4j作为一个简单日志门面,为各种loging APIs(像java.util.logging, logback, log4j)提供一个简单统一的接口,有利于维护和各个类的日志处理方式统一。Logback作为一个具体的日志组件,完成具体的日志操作。
青石路
2018/09/10
1.5K0
从源码来理解slf4j的绑定,以及logback对配置文件的加载
推荐阅读
相关推荐
Slf4j 包老冲突,每次排查半天,是什么原因?怎么解决?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验