SpringConfigurator初始化早于Log4j2致日志配置异常求助
问题场景
使用Spring的SpringConfigurator配置WebSocket端点时,常规声明方式如下:
@ServerEndpoint(value = "/my-endpoint", configurator = SpringConfigurator.class)
但SpringConfigurator通过静态代码块初始化Logger:
private static final Log logger = LogFactory.getLog(SpringConfigurator.class);
结合Log4j2使用时,由于服务器提供的SCI(如org.apache.tomcat.websocket.server.WsSci)执行优先级高于应用级的org.apache.logging.log4j.web.Log4jServletContainerInitializer,导致SpringConfigurator在Log4j2完成配置前就完成类加载与Logger初始化,进而触发日志文件创建错误。
已知可行但不够便捷的方案包括:编程式注册端点、重写SpringConfigurator,希望保留声明式注册方式。
问题
- 是否存在更便捷的解决方案?
- 该场景较为常见,是否应向Spring提交Bug报告?
解决方案与问题解答
1. 更便捷的解决方案
目前最便捷且保留声明式风格的方案是使用初始化-on-demand holder idiom延迟SpringConfigurator的初始化,避免类加载时就触发Logger初始化。自定义代理Configurator实现如下:
public class MyConfigurator extends Configurator { @Override public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException { return Delegate.INSTANCE.getEndpointInstance(endpointClass); } private static final class Delegate { private static final SpringConfigurator INSTANCE = new SpringConfigurator(); } }
使用时仅需修改@ServerEndpoint的configurator参数为自定义类:
@ServerEndpoint(value = "/my-endpoint", configurator = MyConfigurator.class)
此方案无需改动原有端点逻辑,仅新增一个轻量级代理类,完全保留声明式注册的便捷性。
此外,尝试调整SCI执行顺序的可行性极低:服务器级SCI(如Tomcat的WsSci)的优先级由容器控制,无法通过应用配置调整,因此代理延迟初始化是当前最优解。
2. 是否应向Spring提交Bug报告?
建议提交Bug报告。该场景属于WebSocket与Log4j2(或其他依赖SCI初始化的日志框架)结合的常见场景,SpringConfigurator在类加载阶段通过静态代码块初始化Logger的设计,存在与容器初始化顺序冲突的风险,导致日志框架未完成配置前就触发日志操作。
合理的优化方向包括:将Logger改为懒加载(如通过方法级初始化而非静态代码块),或调整SpringConfigurator的初始化时机,避免在类加载阶段就触发依赖外部框架的操作。
内容的提问来源于stack exchange,提问作者Michał Sobkiewicz

