背景

最近有一个业务使用了SpringCloud Config Gateway 源代码如下,由于公司监控系统使用的是Servlet规范来实现的,而基于Weblfux的 Config Gateway 自然是不能使用的。同时业务方也希望可能使用到公司的监控系统。看来还是有点作用的,嘎嘎嘎。

然后引发了一个类加载问题,出错信息如下所示.
image.png

处理问题

监控SpringCloud gw的时候,我会根据当前上下文是否存在GlobalFilter来判断,它是SpringCloud gw环境还是符合Servlet规范的环境,并因此写了一个SpringCloud相关的Conditional

image.png

  1. public class SpringCloudCondition extends SpringBootCondition {
  2. @Override
  3. public ConditionOutcome getMatchOutcome(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
  4. final boolean present = ClassUtils.isPresent("org.springframework.cloud.gateway.filter.GlobalFilter", this.getClass().getClassLoader());
  5. return new ConditionOutcome(present, "none get properties");
  6. }
  7. }

当时这么写完以后,我就发布到线上了,然后业务方也验证通过了。

但是到了晚上发布的时候就出现了开始图中的堆栈。 而出现这个故障的原因也很简单,我原以为的预期是加了Conditonal以后,基于Servlet规范的SpringBoot项目就不会去加载这个Bean了,而实际上,在调用 xxx.class.getDeclaredMethods 的时候会触发一次 SpringCloudFilter 的类加载,而该 Filter 实现了 Config GatewayGlobalFilter ,由此引发了 ClassNotFoundException 的案情。

基于此,我将这两个Bean移动到另外一个配置中,并且对另外配置的Class添加Conditonal,避免 getDeclaredMethods 的调用。

小结:
class加载发生在Spring#Conditional之后,而错误发生在class加载的时候。所以这种写法不能解决问题。

回顾

  • new,设置或读取类静态字段 ,调用静态方法
  • java.lang.reflect 进行反射会进行加载
    • 常用反射的类加载
  • 初始化类时父类会被初始化
  • 启动执行并包含main方法的类
  • 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。