简体   繁体   中英

Using CompletableFuture with @Async returns an empty response for spring boot API

Here is my controller. I used postman to test if it's working but I am getting an empty response. I used @EnableAsync in application configuration and @Async on the service. If I remove @Async on service layer it works but it doesn't run asynchronously.

@ApiOperation(value = "search person by passing search criteria event/title/role/host/is_current", response = ElasticSearchResultData.class)
@RequestMapping(value = "/async2/searchPerson", produces = "application/json", method = RequestMethod.POST)
public @ResponseBody CompletableFuture<ElasticSearchResultData> searchPersonAsync2(@RequestBody SearchCriteriaTo criteriaForDivNetFolderTo,
        HttpServletRequest request, HttpServletResponse response){
    LOGGER.info("searchPerson controller start");
    SearchCriteria searchCriteria = criteriaForDivNetFolderTo.getSearchCriteria();
    if (Util.isNull(searchCriteria)) 
        throw new IllegalArgumentException("search criteria should not be null.");

    try {
        CompletableFuture<ElasticSearchResultData> searchPerson = cubService.searchPersonAsync2(criteriaForDivNetFolderTo);
        ObjectMapper mapper = new ObjectMapper();
        LOGGER.info("search Person "+mapper.writeValueAsString(searchPerson));
        return searchPerson;
    } catch (Exception e) {
        LOGGER.error("Exception in searchPersonAsync controller "+e.getMessage());
    }
    return null;
}

Service

@Async
@Override
public CompletableFuture<ElasticSearchResultData> searchPersonAsync2(SearchCriteriaTo searchCriteriaForDivNetFolderTo) {
   Long start = System.currentTimeMillis();
   LOGGER.info(":in searchPerson");
   CompletableFuture<ElasticSearchResultData> completableFuture = new CompletableFuture<>();
   ElasticSearchResultData searchResultData = null;
   SearchCriteria searchCriteria = searchCriteriaForDivNetFolderTo.getSearchCriteria();
   try {
        LOGGER.info("************ Started searchPerson by criteria ************");
        StringBuilder url = new StringBuilder();
        url.append(equilarSearchEngineApiUrl)
        .append(focusCompanySearchUrl)
        .append("/")
        .append("searchPerson")
        .append("?view=").append(VIEW_ALL)
        .append("&isProcessing=true");

        LOGGER.debug("Calling equilar search engine for focused company search, URL : " + url);
        LOGGER.info(searchCriteria.toString());
        String output = null;
        if (redisEnable != null && redisEnable) {
            output = cacheDao.getDataFromRestApi(url.toString(), RequestMethod.POST.name(), searchCriteria);
        } else {
            output = Util.getDataFromRestApi(url.toString(), RequestMethod.POST.name(), searchCriteria);
        }
        if (!Util.isEmptyString(output)) {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            searchResultData = objectMapper.readValue(output,
                            objectMapper.getTypeFactory().constructType(ElasticSearchResultData.class));
        }
        List<PersonSearchDetails> newPersonDetails = new ArrayList<PersonSearchDetails>();
        if (!Util.isNull(searchResultData) && !Util.isNullOrEmptyCollection(searchResultData.getPersonDetails())
                && !Util.isNullOrEmptyCollection(searchCriteriaForDivNetFolderTo.getNetworkFoldersData())) {
            for (PersonSearchDetails personDetail : searchResultData.getPersonDetails()) {
                String logoUrl = null;
                if(!Util.isNull(searchCriteria.getTargetFolderId())){
                    List<DiversityNetworkFolderTo> filteredFolderTos = searchCriteriaForDivNetFolderTo
                            .getNetworkFoldersData()
                            .stream()
                            .filter(folder -> folder.getId()
                            .longValue() == searchCriteria
                            .getTargetFolderId())
                            .collect(Collectors.toList());
                    logoUrl = getLogoUrl(personDetail.getPersonId(),
                            filteredFolderTos);
                } else {
                    logoUrl = getLogoUrl(personDetail.getPersonId(),
                            searchCriteriaForDivNetFolderTo.getNetworkFoldersData());
                }
                personDetail.setLogoUrl(logoUrl);
                newPersonDetails.add(personDetail);
            }
            searchResultData.setPersonDetails(newPersonDetails);
        }
        completableFuture.complete(searchResultData);
        return completableFuture;
    } catch (Exception e) {
        completableFuture.completeExceptionally(e);
        LOGGER.error(
                " ************** Error in proccessing searchPerson by criteria ************** " + e.getMessage());
    }
    Long end = System.currentTimeMillis();
    LOGGER.info(TIME_DURATION+(end - start)+"ms");
    return null;
}

It would be good to read more about async processing. javadocs are usually a great start!

If you really want to get the result from a Future method, you need to wait for it.

There is a method public T get() method in the CompletableFuture API to wait for wait for the result to be created and return the result once it's done.

If your job is to search a database for the result and then return it - you will still have to wait for it async is not much help in here. It would help you if you had to make multiple things at the same time, eg a call to DB, a web service and something else at the same time, then you can create an array of futures and wait for all of them to complete.

Or, let's say you're creating a POST method, so you can quickly validate the input and send to store to DB async while quickly returning the response to UI and hoping that your async method will be completed in another thread and not returning any errors to UI.

This is a great technique when you know what you're doing, but think if & when you really need it before using it.

The short way to "fix" this is:

CompletableFuture<ElasticSearchResultData> searchPerson = cubService.searchPersonAsync2(criteriaForDivNetFolderTo);
ElasticSearchResultData result = searchPerson.get();
ObjectMapper mapper = new ObjectMapper();
LOGGER.info("search Person "+mapper.writeValueAsString(result));
return result;

( and obviously change the method return signature )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM