一度Nacosのissue修復の合併によるNPE異常を記す
11113 ワード
ISSUE
Spring bootアプリケーション起動終了#21
エラー解析
DeferredApplicationEventPublisher
の継承関係import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
public class DeferredApplicationEventPublisher implements ApplicationEventPublisher, ApplicationListener<ContextRefreshedEvent> {
...
}
DeferredApplicationEventPublisher
の依存図NPE
エラーの原因を分析しますまず
EventPublishingConfigService
のaddListener
を見てみましょう@Override
public void addListener(String dataId, String group, Listener listener) throws NacosException {
Listener listenerAdapter = new DelegatingEventPublishingListener(configService, dataId, group, applicationEventPublisher, executor, listener);
configService.addListener(dataId, group, listenerAdapter);
publishEvent(new NacosConfigListenerRegisteredEvent(configService, dataId, group, listener, true));
}
DelegatingEventPublishingListener
コードの継承関係を見てみましょうimport com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import org.springframework.context.ApplicationEventPublisher;
import java.util.concurrent.Executor;
final class DelegatingEventPublishingListener implements Listener {
DelegatingEventPublishingListener(ConfigService configService, String dataId, String groupId, ApplicationEventPublisher applicationEventPublisher, Executor executor, Listener delegate) {
this.configService = configService;
this.dataId = dataId;
this.groupId = groupId;
this.applicationEventPublisher = applicationEventPublisher;
this.executor = executor;
this.delegate = delegate;
}
}
DelegatingEventPublishingListener
オブジェクトが作成されると、スレッドプールExecutor
とApplicationEventPublisher
(DeferredApplicationEventPublisher
)が渡されます.CacheData.safeNotifyListener()
の方法を見てみましょうprivate void safeNotifyListener(final String dataId, final String group, final String content, final String md5, final ManagerListenerWrap listenerWrap) {
final Listener listener = listenerWrap.listener;
Runnable job = new Runnable() {
public void run() {
ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader appClassLoader = listener.getClass().getClassLoader();
try {
if (listener instanceof AbstractSharedListener) {
AbstractSharedListener adapter = (AbstractSharedListener)listener;
adapter.fillContext(dataId, group);
LOGGER.info("[{}] [notify-context] dataId={}, group={}, md5={}", name, dataId, group, md5);
}
// classloader webapp classloader, spi ( )。
Thread.currentThread().setContextClassLoader(appClassLoader);
ConfigResponse cr = new ConfigResponse();
cr.setDataId(dataId);
cr.setGroup(group);
cr.setContent(content);
configFilterChainManager.doFilter(null, cr);
String contentTmp = cr.getContent();
listener.receiveConfigInfo(contentTmp);
listenerWrap.lastCallMd5 = md5;
LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5,
listener);
} catch (NacosException de) {
LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}", name,
dataId, group, md5, listener, de.getErrCode(), de.getErrMsg());
} catch (Throwable t) {
LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} tx={}", name, dataId, group,
md5, listener, t.getCause());
} finally {
Thread.currentThread().setContextClassLoader(myClassLoader);
}
}
};
final long startNotify = System.currentTimeMillis();
try {
if (null != listener.getExecutor()) {
listener.getExecutor().execute(job);
} else {
job.run();
}
}
...
}
ここで、
safeNotifyListener
は、すべてのListener
にイベントをブロードキャストし、LinkedList
が同時使用される原因となる重要なコードセグメントがある.listener.getExecutor().execute(job);
ここで、先ほど述べた
DelegatingEventPublishingListener
オブジェクトが作成されたときにExecutor
パラメータに入力されたことを覚えていますか?ここでListener
は、Executor
を呼び出して上述したタスクをスレッドプールに呼び出してスケジューリングするので、DeferredApplicationEventPublisher
の同時使用が可能となるエラーの再現
public class DeferrNPE {
private static LinkedList list = new LinkedList<>();
private static CountDownLatch latch = new CountDownLatch(3);
private static CountDownLatch start = new CountDownLatch(3);
private static class MyListener implements Runnable {
@Override
public void run() {
start.countDown();
try {
start.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(String.valueOf(System.currentTimeMillis()));
latch.countDown();
}
}
public static void main(String[] args) {
MyListener l1 = new MyListener();
MyListener l2 = new MyListener();
MyListener l3 = new MyListener();
new Thread(l1).start();
new Thread(l2).start();
new Thread(l3).start();
try {
latch.await();
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
iterator.remove();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
最終修正
非スレッドセキュリティは同時のシーンで使用されるため、上位
nacos-spring-context
のコンテナ使用のみを変更し、元の非スレッドセキュリティのLinkedList
をスレッドセキュリティのConcurrentLinkedQueue
に変更します.転載先:https://juejin.im/post/5cee66f66fb9a07eeb138b55