这个重构技巧的核心就是不要在Runnable#run
方法里写业务逻辑,将业务逻辑抽成一个单独的方法,测试起来更方便。
重构前:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.apache.commons.lang3.StringUtils;
public class Service {
private BlockingQueue<String> queue = new ArrayBlockingQueue<>(128);
public void start() {
new Thread(new EventRunnable()).start();
}
public void doService() {
String event = null;
xxx;
xxxx;
xxxx;
queue.offer(event);
}
private class EventRunnable implements Runnable {
@Override
public void run() {
try {
String event = queue.take();
if (StringUtils.isNotBlank(event)) {
xxx;
xxxx;
xxx;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这段代码很好理解,Service#doService
处理后将event
放到队列中,然后由消费线程获取并处理EventRunnable#run
。
这段代码最大的问题就是可测性差,重构一下:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.apache.commons.lang3.StringUtils;
public class Service {
private BlockingQueue<String> queue = new ArrayBlockingQueue<>(128);
public void start() {
new Thread(new EventRunnable()).start();
}
public void doService() {
String event = null;
xxx;
xxxx;
xxxx;
enQueue(event);
}
private void enQueue(String event) {
queue.offer(event);
}
private class EventRunnable implements Runnable {
@Override
public void run() {
try {
String event = queue.take();
processEvent(event);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void processEvent(String event) {
if (StringUtils.isNotBlank(event)) {
xxx;
xxxx;
xxx;
}
}
}
抽出enQueue
和processEvent
方法。这样只要测试processEvent
就能知道逻辑是否正确,不涉及多线程。
更多的,还可以通过Mock
把异步代码变成同步调用。
public void mock() {
new MockUp<Service> () {
@Mock
public void start() {
// 不用启动异步线程
}
@Mock
public void enQueue(Invocation invocation, String event) {
// 直接变成同步调用,串联起逻辑
Service instance = invocation.getInvokedInstance();
instance.processEvent(event);
}
};
}
以上代码以
JMockit
为例,用Mockito
也差不多。