背景

在看 gitee的一个开源项目 => gitee-base-admin 的时候,发现其中的项目日志通过websocket推送到客户端挺有意义的.个人项目可以加上这个小功能。

但是 其实现是每个用户都新建线程去推日志, 线程数和用户数 1:1. 并且无法对集群日志监听.遂想实现更通用(优雅)的方法实现

改写思路

  1. 将原本的读取本地日志文件 => logging的append机制 => append 多份 => 追加一份到自定义的 Appender
  2. Appender将消息写入到Dispatcher中进行分发处理
  3. 将Dispatcher抽象为接口,做多份实现 以满足 单机基于内存的,集群基于mq中间件的情况
  4. 将用户的websocket上下线推送 => 缓存n行(CircularFifoQueue)做首次推送,其他的消息做轮询用户推送,可以异步化防止单条消息阻塞时间长的问题。 新用户推送需要注意防止消息丢失问题
  5. coding

坑点出现了

在项目启动的时候,启动没有报错,就是没有新消息过来,但是append也写入消息,但是就是取不到消息可以进行消费,后发现两个Dispatcher不是一个实例,饿汉式的单例失效了? 竟然不是一个类加载器?

原因如下:

spring-boot-devtools 对项目的代码是 RestartClassLoader,jar包的代码则是App,又因为我们是通过logback构造的append,所以就算是项目的代码,构造方式,或者流程不一,导致类加载器不一样,最终体现在单例不是单例了。

怎么解决呢? 不用devtools即可 !!! 或者百度dev-tools 配置 or jrebl不香吗

编写过程中的注意点

因为logging的优先级问题,所以不能采用bean的方式构造,我采用的是java spi机制实现多环境切换

ClassLoader

  1. jar包隔离
  2. 动态debug

加载类实例的几种方式 ?

  1. Class.forName
  2. new
  3. ClassLoader.loadClass
  4. other 一些序列化什么的
  5. spi => ServiceLoader

ClassLoader 一些点

  1. ClassLoader.loadClass 类加载器和当前加载的Class不一致时 会导致
com.boommanpro.Parent cannot be cast to com.boommanpro.Parent
  1. new 方式使用的是当前代码所在类加载器加载,而不是调用当前代码的代码所在类加载器加载

  2. Class.forName运作时,通过Reflection.getCallerClass(),能够获取是谁调用了Class.forName,最终还是和new 一致性

  3. 代码 Class有类加载器,线程有类加载器 ServiceLoader用线程的

什么时候类会被加载

参照 java类在何时被加载

说明了以下几种方式

  1. main类
  2. 创建类的实例 new创建子类的实例
  3. 访问类的静态方法
  4. 访问类的静态变量
  5. 反射

补充两点:

  1. cast -> Object o = new Object(); Parent o2 = (Parent) o; 本质是 Parent.class.cast(o);
  2. instanceof => java.lang.Class#isInstance