我们在开发中,调用第三方接口时,往往是提交数据,要异步去获取数据;今天我们用一个利用spring的安排来实现一下异步调用和异步重试的功能;
这个功能的结构图
第一步:创建一个注解Retry
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
/**
* 延时执行时间 (单位:秒)
* @return
*/
long delayed() default 0;
/**
* 重复执行间隔时间 (单位:秒)
* @return
*/
long interval() default 0;
/**
* 重复的次数 最大10次
* @return
*/
int retryTimes() default 1;
}
创建类:
RetryInvorkParam 类 ;用于记录重复执行的一些参数
/**
* 重试执行参数
* @author chenhuaping
* @date 2018年4月20日 下午2:54:39
*/
@Setter
@Getter
@ToString
public class RetryInvorkParam implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 延迟时间
*/
private long delayed;
/**
* 间隔时间
*/
private long interval;
/**
* 重复次数
*/
private int retryTimes;
/**
* 执行的方法的对象
*/
private Object target;
/**
* 执行的参数
*/
private Object[] args;
/**
* 执行的方法
*/
private Method invorkMethod;
/**
* 当前执行的次数
*/
private volatile long currentTimes;
/**
* 下一次执行的时间
*/
private volatile Date nextInvorkTime;
/**
* 是否结束这重试
*/
private volatile boolean isEnd;
}
创建异步执行的时候的任务:
/**
* 方法执行任务
*
* @author chenhuaping
* @date 2018年4月20日 下午3:54:13
*/
public class RetryRunning implements Runnable {
private final static Logger logger = LoggerFactory.getLogger(RetryRunning.class);
/**
* 执行的参数
*/
private RetryInvorkParam invorkParam;
public RetryRunning(RetryInvorkParam invorkParam) {
super();
this.invorkParam = invorkParam;
}
@Override
public void run() {
Method invorkMethod = invorkParam.getInvorkMethod();
Object[] args = invorkParam.getArgs();
Object target = invorkParam.getTarget();
Object result = null;
try {
result = invorkMethod.invoke(target, args);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
logger.error("@Retry 执行方法错误, {}", invorkParam, e);
invorkParam.setEnd(true);
}
if (result != null && result instanceof Boolean && (boolean) result == true) {
invorkParam.setEnd(true);
}
if (invorkParam.isEnd()) {
logger.info("@Retry method = {}, 重复执行了{}次,已经执行结束,param = {}",
target.getClass() + "." + invorkMethod.getName(), invorkParam.getCurrentTimes(), invorkParam);
return;
}
logger.info("@Retry 已经完成了{}方法,第{}次执行,总共需要执行{}次", target.getClass() + "." + invorkMethod.getName(),
invorkParam.getCurrentTimes(), invorkParam.getRetryTimes());
}
}
创建一个定时任务;每一秒去轮询一次重试任务列表:并把已经到时间的任务提交到线程池中:
/**
* 线程提交执行引擎
*
* @author chenhuaping
* @date 2018年4月20日 下午4:22:00
*/
@Component
public class RetryScheduledEngine extends TimerTask implements InitializingBean {
private final static List<RetryInvorkParam> invorkParamList = new ArrayList<>();
@Autowired
private org.springframework.core.task.AsyncTaskExecutor asyncTaskExecutor;
/**
* 开始任务的
*/
private synchronized void startEngine() {
if (CollectionUtil.isNotEmpty(invorkParamList)) {
Iterator<RetryInvorkParam> iterator = invorkParamList.iterator();
while (iterator.hasNext()) {
RetryInvorkParam next = iterator.next();
if (next.isEnd()) {
iterator.remove();
break;
}
// 执行时间
Date now = new Date();
Date nextInvorkTime = next.getNextInvorkTime();
if (nextInvorkTime != null && nextInvorkTime.before(now)) {
// 增加执行次数
long currentTimes = next.getCurrentTimes();
currentTimes++;
next.setCurrentTimes(currentTimes);
// 判断是否为最后一次
if (currentTimes == next.getRetryTimes()) {
next.setEnd(true);
}
// 计算下一次执行时间;
nextInvorkTime = DateUtils.addSeconds(nextInvorkTime, Long.valueOf(next.getInterval()).intValue());
next.setNextInvorkTime(nextInvorkTime);
// 添加任务
asyncTaskExecutor.submit(new RetryRunning(next));
}
}
}
}
/**
* 提交任务
*
* @param param
*/
public void submitTask(RetryInvorkParam param) {
// 计算执行时间
Date now = new Date();
if (param.getDelayed() > 0) {
param.setNextInvorkTime(DateUtils.addSeconds(now, Long.valueOf(param.getDelayed()).intValue()));
} else {
param.setNextInvorkTime(now);
}
invorkParamList.add(param);
}
@Override
public void run() {
this.startEngine();
}
@Override
public void afterPropertiesSet() throws Exception {
Timer timer = new Timer();
timer.schedule(this, 5000, 1000);
}
// @Retry(delayed=5,interval=10,retryTimes=10)
public boolean testRetryInvoke() {
System.out.println("______________testRetryInvoke被执行_________________");
return false;
}
}
最后一步是创建切面配置类:
/**
* 编写切面
*
* @author Anson
*
*/
@Aspect // 注解声明一个切面
@Component // 受spring管理的容器
public class RetryAspect {
@Autowired
private RetryScheduledEngine RetryScheduledEngine;
@Pointcut("@annotation(com.dafy.common.core.threadpool.retry.Retry)") // 注解声明切点
public void annotationPointcut() {
};
@Around("annotationPointcut()")
public Object before(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
Retry[] retryAnnotations = method.getAnnotationsByType(Retry.class);
Retry retryAnnotation = retryAnnotations[0];
long delayed = retryAnnotation.delayed();
long interval = retryAnnotation.interval();
int retryTimes = retryAnnotation.retryTimes();
Object target = joinPoint.getTarget();
Object[] args = joinPoint.getArgs();
//构建参数
RetryInvorkParam param = new RetryInvorkParam();
param.setArgs(args);
param.setCurrentTimes(0);
param.setDelayed(delayed);
param.setEnd(false);
param.setInterval(interval);
param.setInvorkMethod(method);
param.setRetryTimes(retryTimes);
param.setTarget(target);
RetryScheduledEngine.submitTask(param);
return false;
}
}
以为这样就大功告成了吗?
--no no no
还有两个很重要的东西;增加两个包;(spring aop默认有接口的类是选用jdk的接口代理的方式来实现代理的;这种方式会导致,不在接口上的类是没有办法实现aop的)我们要增加两个cglib的实现切面的包,然后利用cglib的字节码重写技术实现代理,这样就不会出现调用报错的情况:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
版本都是1.8.3的
还有一个就是要修改aop的默认实现方式;spring boot需要在启动类中增加一个配置
@EnableAspectJAutoProxy(proxyTargetClass = true)
这样就搞定了;
缺点:1、 这样这些数据都保持在内存中,如果那些执行方法的参数引用的内容发生了变化;就会导致重试结果的变化;(参数最好能够用基本类型)
2、重试的信息是保存在内存中的;重启或者是服务挂了,就会丢失;
3、大量的重试应用数据的保存,可能会导致jvm的内存泄漏;
Tags:
转载于: http://www.zhongruitech.com/554422852.html