适配器错误的打开方式

96 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

问题背景

项目中使用了同事封装的线程池,为了便于排查问题,日志增加了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的实现丢失了

总结

  1. 适配器一定要适配目标类的所有动作,包括目标类的父类
  2. 该案例使用的适配器模式存在一个很严重的理解误区,SimpleCountDownLatchExecutor#execute的入参为SimpleFuture子类的泛型,而SimpleFuture是SimpleRunnable的子类。执行器SimpleThreadExecutor#submit(SimpleRunnable)执行方法的入参是SimpleRunnable类型。是否感觉到了不对?没错,同为SimpleRunnable子类,没有适配的意义,适配器重点在于适配功能,举个不是很恰当的例子:就像插座适配器,可以将三孔与二孔互相适配,如果三孔适配到三孔意义不大(不排除三孔转三孔的适配器适配的是赫兹)