有3个方法,签名完全不同,但执行时都可能发生异常。要求:无论哪个方法异常,都需要重试3次。假设重试3次的逻辑很复杂,但3个方法的逻辑都是相同的。如何复用重试代码?
public class FFMPEGManagerRedo implements FFMPEGManager {
private static final Logger LOG = LoggerFactory.getLogger(FFMPEGManagerRedo.class);
/** 重试的次数和间隔 */
private int[] retryInterval;
private FFMPEGManager ffmpegManager;
public FFMPEGManagerRedo(FFMPEGManager ffmpegManager, int[] retryInterval) {
super();
if (retryInterval == null || retryInterval.length == 0) {
throw new IllegalArgumentException("retry times should be greater than 0");
}
this.ffmpegManager = ffmpegManager;
this.retryInterval = retryInterval;
}
public FFMPEGManagerRedo(FFMPEGManager ffmpegManager) {
this(ffmpegManager, new int[] { 1, 2, 4 });//指数递增
}
@Override
public MetaInfo readMetaInfo(String videoFileName) throws Exception {
try {
return ffmpegManager.readMetaInfo(videoFileName);//重试的时候才反射(减少反射对性能影响)
} catch (Exception e) {
Class<?>[] argsType = new Class<?>[] { String.class };
Object[] argsValue = new Object[] { videoFileName };
return (MetaInfo) retryMethod(ffmpegManager.getClass().getMethod("readMetaInfo", argsType), ffmpegManager,
argsValue);
}
}
@Override
public void extractThumb(String videoFileName, ThumbSpec thumbSpec, String thumbFileName) throws Exception {
try {
ffmpegManager.extractThumb(videoFileName, thumbSpec, thumbFileName);//重试的时候才反射(减少反射对性能影响)
} catch (Exception e) {
Class<?>[] argsType = new Class<?>[] { String.class, ThumbSpec.class, String.class };
Object[] argsValue = new Object[] { videoFileName, thumbSpec, thumbFileName };
retryMethod(ffmpegManager.getClass().getMethod("extractThumb", argsType), ffmpegManager, argsValue);
}
}
@Override
public void transcode(String videoFileName, TranscodeSpec transcodeSpec, String transcodeFileName) throws Exception {
try {
ffmpegManager.transcode(videoFileName, transcodeSpec, transcodeFileName);//重试的时候才反射(减少反射对性能影响)
} catch (Exception e) {
Class<?>[] argsType = new Class<?>[] { String.class, TranscodeSpec.class, String.class };
Object[] argsValue = new Object[] { videoFileName, transcodeSpec, transcodeFileName };
retryMethod(ffmpegManager.getClass().getMethod("transcode", argsType), ffmpegManager, argsValue);
}
}
/* Retry Template-Callback */
protected Object retryMethod(Method method, Object instance, Object... args) throws Exception {
int retryCnt = 1;
Exception lastException = null;
do {
try {
Object r = method.invoke(instance, args);//method callback
LOG.info("method {} invoke retry succ at time {}", method.getName(), retryCnt);
return r;
} catch (java.lang.reflect.InvocationTargetException targetException) {
Throwable targetThrowable = targetException.getTargetException();
if (targetException instanceof Exception) {//TargetException会被转译
lastException = (Exception) targetThrowable;
} else {
lastException = targetException;
}
} catch (Exception e) {
lastException = e;
}
if (lastException != null) {
LOG.warn("method {} invoke fail at time {}, retry ...", method.getName(), retryCnt, lastException);
if (retryCnt < retryInterval.length) {//最后一次不用睡眠
Thread.sleep(1000L * retryInterval[retryCnt]);//retry interval
}
retryCnt++;
}
} while (retryCnt < retryInterval.length);
throw lastException;
}
}
TestCase:
public class FFMPEGManagerRedoTest {
private static final Logger LOG = LoggerFactory.getLogger(FFMPEGManagerRedoTest.class);
//非public时,反射获取不到
public static class CountedFFMPEGManager implements FFMPEGManager {
private int metaCnt = 0;
private int thumbCnt = 0;
private int transCnt = 0;
private int maxFailCnt = 3;
public CountedFFMPEGManager(int maxFailCnt) {
super();
this.maxFailCnt = maxFailCnt;
}
@Override
public MetaInfo readMetaInfo(String videoFileName) throws Exception {
metaCnt++;
if (metaCnt < maxFailCnt) {
throw new Exception("mock fail");
}
MetaInfo meta = new MetaInfo();
meta.setSizeByte(1024);
return meta;
}
@Override
public void extractThumb(String videoFileName, ThumbSpec thumbSpec, String thumbFileName) throws Exception {
thumbCnt++;
if (thumbCnt < maxFailCnt) {
throw new Exception("mock fail");
}
}
@Override
public void transcode(String videoFileName, TranscodeSpec transcodeSpec, String transcodeFileName)
throws Exception {
transCnt++;
if (transCnt < maxFailCnt) {
throw new Exception("mock fail");
}
}
public int getMetaCnt() {
return metaCnt;
}
public int getThumbCnt() {
return thumbCnt;
}
public int getTransCnt() {
return transCnt;
}
}
private CountedFFMPEGManager counted;
private FFMPEGManagerRedo redo;
@Before
public void setUp() throws Exception {
counted = new CountedFFMPEGManager(3);//最多允许失败3次
redo = new FFMPEGManagerRedo(counted, new int[] { 1, 2, 4 });//如果失败,还可以重试3次
}
@Test
public void testMeta() throws Exception {
MetaInfo meta = redo.readMetaInfo("videoFileName");
LOG.info("meta cnt: {}", counted.getMetaCnt());
Assert.assertEquals(3, counted.getMetaCnt());
Assert.assertNotNull(meta);
Assert.assertEquals(1024, meta.getSizeByte());
}
@Test
public void testThrumb() throws Exception {
ThumbSpec thumbSpec = new ThumbSpec(120, 90, 2);
redo.extractThumb("videoFileName.mp4", thumbSpec, "thumbFileName.jpg");
LOG.info("thumb cnt: {}", counted.getThumbCnt());
Assert.assertEquals(3, counted.getThumbCnt());
}
@Test
public void testTrans() throws Exception {
TranscodeSpec transcodeSpec = new TranscodeSpec();
redo.transcode("videoFileName.flv", transcodeSpec, "transcodeFileName.mp4");
LOG.info("trans cnt: {}", counted.getTransCnt());
Assert.assertEquals(3, counted.getTransCnt());
}
@Test
public void testBeyondRetryTimes() {
redo = new FFMPEGManagerRedo(counted, new int[] { 1 });//只重试1次,Mock会连续失败3次,最终结果是失败
Exception exception = null;
MetaInfo meta = null;
try {
meta = redo.readMetaInfo("videoFileName");
} catch (Exception e) {
exception = e;
System.err.println(e);
}
Assert.assertNull(meta);
Assert.assertNotNull(exception);
Assert.assertEquals("mock fail", exception.getMessage());
}
}