Spring中Log4j自定义Appender读取静态Map为空的原因
问题分析与解决方案
这个问题的核心原因是Log4j2自定义Appender的加载机制和Spring上下文完全隔离,导致你的静态类A被加载了两次,产生了两个独立的静态变量副本。
为什么会出现这种情况?
Log4j2的插件(包括你自定义的MapAppender)是在应用启动的极早期阶段,由Log4j2自身的类加载器初始化的——这个时间点甚至早于Spring容器开始创建Bean的时间。而你的服务层BService是由Spring的Web应用类加载器加载和管理的。
如果A类被这两个不同的类加载器分别加载,那么每个类加载器都会创建一个A类的独立副本,对应的静态listMap自然也是完全分离的:
- 服务层调用
A.addMap()时,操作的是Spring类加载器加载的A类的静态Map; - 而
MapAppender的append()方法中,访问的是Log4j2插件类加载器加载的A类的静态Map,两者完全不是同一个对象,所以会出现“服务层能拿到数据,Appender拿不到”的现象。
另外,你的代码里还有几个小问题需要先修正:
A.getMap()的返回类型错误:listMap.values()返回的是Collection<Session>,不是Map<String>;A.addMap()方法声明为void但写了return,这会导致编译错误;- 多线程环境下(服务层和Appender可能同时操作Map),
HashMap不是线程安全的,建议换成ConcurrentHashMap。
解决方法
最可靠的方式是把静态类A改成Spring管理的单例Bean,让Appender通过Spring上下文获取正确的实例,避免类加载器隔离的问题。
步骤1:将A改为Spring单例Bean
@Component public class A { // 换成线程安全的ConcurrentHashMap private final Map<String, Session> listMap = new ConcurrentHashMap<>(); // 返回不可修改的集合,避免外部直接修改内部Map public Collection<Session> getSessionCollection() { return Collections.unmodifiableCollection(listMap.values()); } public Session addSession(String name, Session s) { return listMap.put(name, s); } }
步骤2:创建Spring上下文持有类
用来在非Spring管理的类(比如你的MapAppender)中获取Spring Bean:
@Component public class ApplicationContextHolder implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { applicationContext = ctx; } public static <T> T getBean(Class<T> beanClass) { return applicationContext.getBean(beanClass); } }
步骤3:在MapAppender中获取Spring管理的A实例
修改append()方法,通过ApplicationContextHolder拿到正确的A Bean:
@Override public void append(LogEvent event) { // 获取Spring管理的A实例 A aBean = ApplicationContextHolder.getBean(A.class); Collection<Session> sessionCollection = aBean.getSessionCollection(); // 执行你的send逻辑 send(); }
其他注意事项
- 确保
ApplicationContextHolder被Spring正确扫描到(和其他Bean在同一个包下,或者通过@ComponentScan指定扫描路径); - 如果你的应用是Web应用,还要注意Log4j2的初始化时机,确保Spring上下文完全加载后Appender才开始处理日志事件(可以通过配置Log4j2的启动顺序,或者在Appender中添加判断,避免Spring上下文未初始化时调用
getBean)。
内容的提问来源于stack exchange,提问作者bearics
相关产品推荐
相关产品推荐

