本文已参与「新人创作礼」活动,一起开启掘金创作之路。
问题背景
项目中使用了同事封装的线程池,为了便于排查问题,日志增加了MDC自定义traceId,使用线程池封装的线程类,实现了before方法put traceId,after方法clear traceId,但是发现实现方法未生效。。。
应用
线程池代码
线程池包装类
public class SimpleCountDownLatchExecutor {
...
public <T extends SimpleFuture> boolean execute(List<T> runnables, int latchTimeout)
throws InterruptedException {
...
//定义闭锁指定大小
CountDownLatch latch = new CountDownLatch(runnables.size());
for (SimpleFuture runnable : runnables) {
simpleThreadExecutor.submit(new SimpleFutureAdpater(runnable, latch));
}
return latch.await(latchTimeout, TimeUnit.MILLISECONDS);
}
...
}
线程执行器
public class SimpleThreadExecutor implements Runnable, InitializingBean {
...
public void submit(SimpleRunnable runnable) {
this.executor.execute(runnable);
}
...
}
线程实现类
public abstract class SimpleRunnable extends BaseRunnable implements Runnable {
/**
* @see Runnable#run()
*/
@Override
public void run() {
try {
beforeRun();
//执行业务
doRun();
} catch (Throwable t) {
logger.error("unexpect error", t);
} finally {
afterRun();
}
}
/**
* 开始执行业务逻辑
*/
public abstract void doRun();
}
代码看起来人畜无害,继续看业务代码
业务代码
ruleEngineThreadExecutor.execute(
bizParams.stream().map(parma -> new SimpleRetryFuture(){
private RuleEngineResult ruleEngineResult;
@Override
protected void beforeRun() {
// 执行前置处理
super.beforeRun();
MDC.put("traceId","test");
}
@Override
protected void afterRun() {
// 执行后置处理
MDC.clear();
super.afterRun();
}
@Override
public void doRun() {
...
}
@Override
public Object get() {
return ruleEngineResult;
}
}).collect(Collectors.toList()), 5000
);
问题分析
代码逻辑相对简单,但是为啥没有生效,只能从线程池里面的封装找原因了,里面有个适配器,看一下源码
适配器
public class SimpleFutureAdpater<T> extends SimpleRunnable {
private final SimpleFuture<T> futureInstance;
private final CountDownLatch latch;
...
@Override
public void doRun() {
try {
futureInstance.doRun();
} finally {
//计数处理
latch.countDown();
}
}
}
看到代码后问题就清晰了,问题就出在适配器仅对SimpleRunnable.doRun做了适配,而未对SimpleRunnable的父类BaseRunnable的动作适配,也就是未适配beforeRun、afterRun。因此我们对beforeRun、afterRun的实现丢失了
总结
- 适配器一定要适配目标类的所有动作,包括目标类的父类
- 该案例使用的适配器模式存在一个很严重的理解误区,SimpleCountDownLatchExecutor#execute的入参为SimpleFuture子类的泛型,而SimpleFuture是SimpleRunnable的子类。执行器SimpleThreadExecutor#submit(SimpleRunnable)执行方法的入参是SimpleRunnable类型。是否感觉到了不对?没错,同为SimpleRunnable子类,没有适配的意义,适配器重点在于适配功能,举个不是很恰当的例子:就像插座适配器,可以将三孔与二孔互相适配,如果三孔适配到三孔意义不大(不排除三孔转三孔的适配器适配的是赫兹)