一、引言
组内最近做了一个日志公共组件,用的是javaagent的方式,之前搞的maven jar包每次都要把所有系统都发一遍,太麻烦。
javaagent通过Java虚拟机(JVM)的Instrumentation API来实现代码的侵入。通过Instrumentation API,Java agent可以在类加载过程中修改字节码,向目标代码中插入自定义的逻辑或进行其他操作。我们的组件使用bytebuddy进行字节码修改。
今天发布系统会在流水线把这个日志组件带进去,然后就出现了SoaServiceLogAction java.lang.NoClassDefFoundError: Could not initialize class **.LogConfig。
二、分析
1、原因
这个报错是很模糊的,根本不知道具体原因,正常有以下几种原因:
1. 缺少依赖:如果类 `**.LogConfig` 依赖于其他类或库,而这些依赖没有正确地被引入到项目中,就会导致找不到类的定义。
2. 类初始化失败:如果类的静态初始化块或静态字段初始化过程中发生异常,就会导致类初始化失败。这可能是因为初始化过程中的代码抛出了异常,或者依赖的资源无法访问或加载。
3. 类路径冲突:如果类 `**.LogConfig`在多个地方存在,可能会导致类路径冲突,从而无法正确加载类的定义。
2、组件
还是要看下代码,这里只列出了出问题的地方,省略了排查其他代码的过程。
public class LogConfig {
private static Map<String, String> config = Config.get("**.properties").asMap();
public static String scenario() {
return config.get(LOG_SCENARIO);
}
}
这个没有类没有被正确初始化才导致了NoClassDefFoundError,这个类在什么时候被调用呢?
3、JavaAgent
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void OnMethodExit(
@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] allArguments,
@Advice.Thrown Throwable throwable) {
try {
if (StringUtils.isBlank(LogConfig.scenario())) {
return;
}
//日志处理
} catch (Throwable e) {
e.printStackTrace();
}
}
`@Advice.OnMethodExit` 是bytebuddy库中的一个注解,用于在方法退出时进行拦截和处理。它可以被用于在方法执行完毕后执行一些特定的逻辑,例如记录日志、统计方法执行时间等。
这里可以看到判断一下对应的场景有没有配置使用日志组件。场景很多,有数据库、链路调用、mq等等,方便针对性的记录日志。
4、初始化分析
LogConfig在每次调用或者操作DB的时候都会被调用其中的静态方法scenario,这时候就应该被初始化,说明这个方法有问题。因为静态方法属于类级别的方法,调用静态方法会导致类的初始化。在初始化过程中,会执行静态初始化块和静态字段的初始化操作。因此,在调用 `scenario()` 方法之前,类 `LogConfig` 的静态字段 `config` 会被初始化。
那么会有什么问题呢?config字段也就是加载一下配置而已。如果配置不存在呢?作者想到这里看了一下配置中心,果然没有这个文件。
三、解决
作者联系了系统对应的负责人加上**.properties这个文件,但是感觉组件应该在本地建一个文件,如果加载不到配置中心的也能加载到本地的。不然推广到其他部门的时候就会产生这种乌龙。
四、总结
java agent做公共代码的抽离是个好方式,但是就像作者上次Log4j-tag丢失-CSDN博客发现的问题一样,不看组件源代码根本不知道发生了什么。也就要求研发人员对JavaAgent有一定的了解。
下一期作者准备讲讲搭建和使用JavaAgent,有兴趣的同学点点关注。