Springboot:如何使Rest Service Controller无阻塞

时间:2018-04-11 16:31:40

标签: java spring spring-boot groovy concurrency

我有一个spring boot应用程序,用于验证给定客户端ID的文件并返回验证错误和警告的json响应,执行负载测试时我们注意到大多数请求被阻止,所以我试图使我们的应用程序无阻塞通过利用Spring的非阻塞api

以下是我的春季版

springBootVersion = '1.5.3.RELEASE'
springVersion = '4.3.8.RELEASE'

以下是我的springboot ValidationController.groovy阻止请求

@Controller
@ResponseBody
class ValidationController {

    @RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    ResponseEntity<ValidationResult> validate(@RequestParam(value = "file", required = true) MultipartFile file,
                                    @RequestParam(value = "client_id", required = true) String clientId)
    {
        if (clientId.isEmpty()) {
                String msg = "client id is required"
                if (LOGGER.isErrorEnabled()) {
                    LOGGER.error(msg)
                }
                return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
            }

            String contentType = file.contentType.toLowerCase();
            if (LOGGER.isDebugEnabled()) LOGGER.debug("content type = $contentType");

            Client client = clientRepository.findByExternalId(clientId)

            if (client == null) {
                String msg = "client id is invalid"
                if (LOGGER.isErrorEnabled()) {
                    LOGGER.error(msg)
                }
                return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
            }

            if (file.isEmpty()) {
            String msg = "file is empty"
            if(LOGGER.isErrorEnabled()) {
                LOGGER.error(msg)
            }
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
        }

        ValidationResult result = validationService.validate(file, client);

        return ResponseEntity.ok(result)
    }
}

class ValidationResult {

    private List<Warning> warnings
    private List<Error> errors
    //getters setters for warnings and errors
}

class Warning {
    private String message
    private String type
    //getters setters for message and type
}

class Error {
    private String message
    private String type
    //getters setters for message and type
}

我修改了我的ValidationController.groovy,如下所示

@Controller
@ResponseBody
class ValidationController {

    @Autowired
    @Qualifier("postRequestExecutorService")
    private ExecutorService postRequestExecutor;


    @RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public DeferredResult<ResponseEntity<ValidationResult>> validate(@RequestParam(value = "file", required = true) MultipartFile file,
                                                              @RequestParam(value = "client_id", required = true) String clientId)
    {
        DeferredResult<ResponseEntity<ValidationResult>> deferredResult = new DeferredResult<>();

       CompletableFuture.supplyAsync(() -> validate(clientId, file), postRequestExecutor)
                .whenComplete((result, throwable) ->
                {                    
                    deferredResult.setResult(result);
                }        );


    }

private ResponseEntity<ValidationResult> validateLedes(String clientId, MultipartFile file) {

    ValidationResult result;
    try{
        if (clientId.isEmpty()) {
            String msg = messageSource.getMessage("client.id.required", null, Locale.getDefault())
            LOGGER.error(msg)
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
        }

        String contentType = file.contentType.toLowerCase();
        if (LOGGER.isDebugEnabled()) LOGGER.debug("content type = $contentType");

        Client client = clientRepository.findByExternalId(clientId)

        if (client == null) {
            String msg = messageSource.getMessage("client.id.invalid", null, Locale.getDefault())
            LOGGER.error(msg)
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
        }

        if (file.isEmpty()) {
            String msg = messageSource.getMessage("ledes.file.empty", null, Locale.getDefault())
            LOGGER.error(msg)
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
        }

        result = validationService.validate(file, Ledesxmlebilling21.class, client);
    }
    catch (Exception ex){
        LOGGER.error("Exception in validateLedes = "+ex.message)
        LOGGER.error("StackTrace in validateLedes = "+ex.stackTrace)
    }

    return ResponseEntity.ok(result)
}

}

以下是我的ExecutorServiceConfiguration

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

@Configuration
public class RequestsExecuterServiceConfiguration {

    /**
     * Dedicated Thread Modeling to handle POST Requests
     */
    @Bean
    public ExecutorService postRequestExecutorService() {
        final ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setNameFormat("postRequestExecutor-%d")
                .setDaemon(true)
                .build();
        ExecutorService es = Executors.newFixedThreadPool(10,threadFactory);
        return es;
    }
}

由于我的控制器是一个groovy类,我看到CompletableFuture lambda表达式的一些编译器错误,有人可以帮助我使它适用于groovy控制器吗?

UPDATE1 根据答案,我已将labda表达式更改为关闭,如下所示

@RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public DeferredResult<ResponseEntity<ValidationResult>> validate(@RequestParam(value = "file", required = true) MultipartFile file,
                                                          @RequestParam(value = "client_id", required = true) String clientId)
{
    DeferredResult<ResponseEntity<ValidationResult>> deferredResult = new DeferredResult<ResponseEntity<ValidationResult>>();

    CompletableFuture.supplyAsync({ -> validateLedes(clientId, file) }, postRequestExecutor)
            .whenComplete({ futureResult, throwable -> deferredResult.setResult(futureResult);})

    deferredResult
}

使用上述控制器,我没有得到错误

2018-04-11 15:07:45 - Exception in validateLedes = failed to lazily initialize a collection of role: com.validation.entity.Client.ruleConfigurations, could not initialize proxy - no Session
2018-04-11 15:07:45 - StackTrace in validateLedes = org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:587), org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:204), org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:148), org.hibernate.collection.internal.PersistentSet.size(PersistentSet.java:143)

看起来问题是,Hibernate会话没有绑定到ExecutorService并且validateLedes方法执行它的新线程无法从数据库中读取,有人可以请我将Hibernate会话绑定到ExecutorService的线程池?

1 个答案:

答案 0 :(得分:1)

你不能只将lambdas粘贴到Groovy(直到Groovy 3)

您需要将它们翻译为Closures,例如:

() -> validate(clientId, file)

变为:

{ -> validate(clientId, file) }

(result, throwable) ->
{                    
    deferredResult.setResult(result);
} 

将是:

{ result, throwable -> deferredResult.setResult(result) }