背景
在開發中有時會遇到長耗時的任務,我們又不想傻傻的等待任務的執行完成后才可能進行其他的操作。這時我們首先想到的是使用多線程進行異步處理,那么如何才能知道任務的執行狀態呢,如執行了多長時間,執行的結果是成功了還是失敗了。
此處提到的長耗時任務是指本身已經經過優化了不能在拆分的情況,如果任務還可以在拆分請先拆分,不要為難你自己。
如果項目中只是一次性或者很少量是這種情況,怎么著也能忍了;此處面對的是大量的,并且是需要經常處理的情況。
如果是定時任務或者在控制臺執行的,就忍著吧,我們總是對自己很殘忍,對他人很友善的。
總之此處要面對的就是項目中有大量的長耗時任務,需要在頁面上點擊某個按鈕后立即返回,還要知道任務執行的情況。
解決方法
辦法總比問題多,總是有解決的方法的。我用到的技術主要是自定義注解、AOP、@Async。不太了解的請自行掃盲,此處不做展開。
具體代碼
自定義注解
import java.lang.annotation.*;
/**
* 后續操作
* @author 李慶海
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aftermath {
/**
* 運行結果保存的路徑
* 相對路徑是指相對prefix返回值而言
* @return
*/
String path() default "";
/**
* 運行結果保存的根路徑
* @return
*/
String prefix() default "/devops/ydhxg/";
}
善后處理攔截器
import cn.com.yd.cygp4.webinterface.YuandayunHelper;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import java.lang.reflect.Method;
/**
* 后續操作攔截器
*
* @author 李慶海
*/
@Aspect
@Component
@Slf4j
public class AftermathInterceptor {
@Pointcut("@annotation(cn.com.yd.cygp4.webinterface.annotations.Aftermath)")
public void aspect() {
}
@Around(value = "aspect()")
public Object around(ProceedingJoinPoint point) throws Throwable {
log.debug("進入事后處理");
Object reO = null;
// 返回目標對象
Object target = point.getTarget();
String targetName = target.getClass().getName();
// 返回當前連接點簽名
String methodName = point.getSignature().getName();
// 獲得參數列表
Object[] arguments = point.getArgs();
Class<?> targetClass = Class.forName(targetName);
// 獲取參數類型數組
Class<?>[] argTypes = ReflectUtils.getClasses(arguments);
// 獲取目標method,考慮方法的重載等問題
Method method = targetClass.getDeclaredMethod(methodName, argTypes);
// 獲取目標method上的注解
Aftermath annotation = method.getAnnotation(Aftermath.class);
if (null != annotation) {
String status = null;
StopWatch clock = new StopWatch();
//計時開始
clock.start();
try {
reO = point.proceed();
status = "成功";
}catch(Exception e){
status = "失敗";
}finally {
clock.stop();
JSONObject json = new JSONObject();
//執行狀態
json.put("status",status);
// 花費的時間
json.put("take",clock.getTotalTimeSeconds());
String path = annotation.path();
if(null == path || "".equals(path)){
path = targetName+"/"+methodName;
}
path = annotation.prefix()+path;
System.out.println("path:"+path);
System.out.println(json);
YuandayunHelper.delete(path);
YuandayunHelper.add(path,json);
}
} else {
reO = point.proceed();
}
log.debug("退出事后處理");
return reO;
}
}
異步執行
開啟異步執行
/**
* Springboot 程序入口
*
* @author 李慶海
*/
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
在目標方法上添加@Async
@Async
public Object chushihuagupiaogainian() {
//do something;
return something;
}
長耗時異步執行示例
import cn.com.yd.cygp4.webinterface.annotations.Aftermath;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 龍虎榜手動管理Controller
*
* @author 李慶海
*/
@RestController
@RequestMapping("${admin-url}")
@Api(tags = "后臺管理")
public class AdminController {
/**
* 初始化
*
* @return
*/
@GetMapping("/init")
@ApiOperation("后臺管理 > 數據初始化")
@Aftermath
@Async
public Object init() {
//此處耗時5m;
return something;
}
最后
這些代碼都是從實際開發中提煉出來的,與業務具有一定的綁定。為了跳出局限使其具有通用性,可以進行一些優化,比如將寫入操作抽象一個接口如IWrite,定義一個方法write(String path,String content),然后根據場景進行實現即可達到通用的目的。關于執行結果的獲取就更好辦了,可以是監聽、可以是輪詢。