在實際項目中須要開發一個處理資源編譯請求的接口:接受前臺頁面上傳的ymal文件,後臺根據模板生成scala文件,而後打包、構建docker鏡像。spring
文件上傳和後臺處理過程的實現都不難,只是整個過程是比較耗時的,這裏若是使用同步方式,那麼上傳-->模板解析-->打包-->構建鏡像-->返回結果;這個過程,前臺的頁面都是等待狀態的,用戶會覺得頁面卡死了。因此,這裏須要作異步處理:docker
1.上傳-->返回正在編譯的標誌;springboot
2.模板解析-->打包-->構建鏡像-->存儲編譯結果;app
此時,當用戶上傳完文件後,頁面立馬跳轉,模板解析和鏡像構建等工做,繼續在後臺進行,而用戶能夠不用等待。異步
因爲整個接口服務採用springboot實現,這裏簡單記錄一種springBoot的異步使用方式,ui
這種方式,是springBoot自身的一種異步方式,使用註解實現,很是方便,咱們在想要異步執行的方法上加上@Async註解,在controller上加上@EnableAsync便可。注意這裏的異步方法,只能在自身以外調用,在本類調用是無效的。this
controllerspa
@RequestMapping(value = "/XXX/xxx") @RestController @EnableAsync public class CompileController { private static final Logger log = LoggerFactory.getLogger(CompileController.class); private final static int MZX_SIZE = 51200000; @Autowired private CompileRecordRepository recordRepository; @Autowired private BackendService backend; @Value("${build.resource-dir}") private String resourceDir; /** * 編譯服務 * * @param name * @param version * @param namespace * @param file * @return */ @RequestMapping(value = "/compile", method = RequestMethod.POST) public String compile(String name, String version, String namespace, @RequestParam("yaml") MultipartFile file) { if (file.isEmpty()) { return Response.error("編譯失敗,由於文件是空的."); } if (file.getSize() > MZX_SIZE) { return Response.error("編譯失敗,文件大小超過限制"); } String fileName = file.getOriginalFilename().toLowerCase(); log.info("fileName:" + fileName); if (!fileName.endsWith(".yml")) { return Response.error("must upload yml format file"); } String token = TokenUtil.generateToken(); CompileLog compileLog = new CompileLog(); compileLog.setId(token); compileLog.setName(name); compileLog.setNamespace(namespace); compileLog.setToken(token); compileLog.setVersion(version); compileLog.setCreateTime(System.currentTimeMillis()); compileLog.setUpdateTime(System.currentTimeMillis()); compileLog.setStatus(0); recordRepository.save(compileLog); //讀取文件內容並寫到本地 String ymlStr = readAndWrite2Local(file); //後臺異步執行 backend.execute(compileLog, ymlStr); return Response.Builder .success() .setMsg("正在編譯,可根據token查詢編譯結果") .appendData("token", token) .build(); } }
serviceImpl線程
@Component public class BackendService { private static final Logger log = LoggerFactory.getLogger(BackendService.class); @Autowired CompileRecordRepository recordRepository; @Value("${build.resource-dir}") String resourceDir; private final static String SCALA_FILE = ""; @Async public String execute(CompileLog compileLog, String ymlStr) { //模板代碼目錄 String capTemplateDir = resourceDir + "/cap"; String capInstanceDir = resourceDir + "/capInstance/" + compileLog.getToken(); FileUtil.mkdir(capInstanceDir); capInstanceDir += "/cap"; //建立實例代碼目錄 FileUtil.copyDir(capTemplateDir, capInstanceDir); log.info(Thread.currentThread().getName() + " start rendering..."); TemplateService.templateRender(ymlStr, capInstanceDir + SCALA_FILE); this.pack(capInstanceDir, compileLog); this.image(capInstanceDir, compileLog); log.info(Thread.currentThread().getName() + " update record..."); compileLog.setStatus(CompileStatus.SUCCESS); compileLog.setUpdateTime(System.currentTimeMillis()); recordRepository.save(compileLog); log.info(Thread.currentThread().getName() + " execute finished."); return "執行異步任務完畢"; } /** * 打成jar包 */ private void pack(String capDir, CompileLog record) { log.info("start package jar..."); } /** * 生成鏡像 */ private void image(String capDir, CompileLog record) { log.info("start build docker image..."); } }
執行結果,略。。。。。。scala
不少狀況下,異步處理是一種很常見並且高效的方式,springBoot自帶的註解方式只用兩個註解便可實現,簡單易用。固然除此以外還有其餘的實現方式,例如能夠經過建立線程池來實現。