web-dev-qa-db-fra.com

Impossible de renvoyer ResponseEntity avec des détails d'exception au printemps

J'ai créé une application Spring Restful Service et Spring MVC.

Service reposant :: Le service reposant renvoie une entité si elle existe dans la base de données. S'il n'existe pas, il renvoie des informations d'exception personnalisées dans l'objet ResponseEntity.

Il fonctionne comme prévu testé avec Postman.

@GetMapping(value = "/validate/{itemId}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ResponseEntity<MyItem> validateItem(@PathVariable Long itemId, @RequestHeader HttpHeaders httpHeaders) {

    MyItem myItem = myitemService.validateMyItem(itemId);
    ResponseEntity<MyItem> responseEntity = null;
    if (myItem == null) {
        throw new ItemNotFoundException("Item Not Found!!!!");
    }
    responseEntity = new ResponseEntity<MyItem>(myItem, headers, HttpStatus.OK);
    return responseEntity;
}

Si l'entité demandée n'existe pas, le service reposant est renvoyé ci-dessous.

@ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
    System.out.println("In CREEH::ItemNFE");
    ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
    ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<ExceptionResponse>(exceptionResponse, HttpStatus.NOT_FOUND);
    return responseEntity;
}

Mais lorsque j'appelle le service ci-dessus à partir d'une application MVC printanière utilisant RestTemplate, il renvoie un objet valide s'il existe.

Si l'objet demandé n'existe pas, le service reposant renvoie les informations sur l'exception, mais ne parvient pas à l'application appelante (Spring MVC).

L'application Spring Spring appelle le service Web reposant à l'aide du modèle Reste

String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();
4
Abdul

C'est le comportement attendu. Le modèle de repos génère une exception lorsque l'état http est une erreur client ou une erreur de serveur et renvoie la réponse lorsque l'état http n'est pas un état d'erreur.

Vous devez fournir une implémentation pour utiliser votre gestionnaire d'erreurs, mapper la réponse à l'entité de réponse et lever l'exception.

Créez une nouvelle classe d'exception d'erreur avec le champ ResponseEntity.

public class ResponseEntityErrorException extends RuntimeException {
  private ResponseEntity<ErrorResponse> errorResponse;
  public ResponseEntityErrorException(ResponseEntity<ErrorResponse> errorResponse) {
      this.errorResponse = errorResponse;
  }
  public ResponseEntity<ErrorResponse> getErrorResponse() {
      return errorResponse;
  }
}

Gestionnaire d'erreur personnalisé qui mappe la réponse d'erreur à ResponseEntity.

public class ResponseEntityErrorHandler implements ResponseErrorHandler {

  private List<HttpMessageConverter<?>> messageConverters;

  @Override
  public boolean hasError(ClientHttpResponse response) throws IOException {
    return hasError(response.getStatusCode());
  }

  protected boolean hasError(HttpStatus statusCode) {
    return (statusCode.is4xxClientError() || statusCode.is5xxServerError());
  }

  @Override
  public void handleError(ClientHttpResponse response) throws IOException {
    HttpMessageConverterExtractor<ExceptionResponse> errorMessageExtractor =
      new HttpMessageConverterExtractor(ExceptionResponse.class, messageConverters);
    ExceptionResponse errorObject = errorMessageExtractor.extractData(response);
    throw new ResponseEntityErrorException(ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(errorObject));
  }

  public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    this.messageConverters = messageConverters;
  }
}

Configuration RestTemplate - Vous devez définir le gestionnaire errorHandler de RestTemplate sur ResponseEntityErrorHandler. 

@Configuration
public class RestTemplateConfiguration {
  @Bean
  public RestTemplate restTemplate() {
      RestTemplate restTemplate = new RestTemplate();
      ResponseEntityErrorHandler errorHandler = new ResponseEntityErrorHandler();
      errorHandler.setMessageConverters(restTemplate.getMessageConverters());
      restTemplate.setErrorHandler(errorHandler); 
      return restTemplate;
   }
}

Méthode d'appel

@Autowired restTemplate

String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
try {
    ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
    int restCallStateCode = responseEntity.getStatusCodeValue();
} catch (ResponseEntityErrorException re) {
    ResponseEntity<ErrorResponse> errorResponse = re.getErrorResponse();
}
3
Veeram

Essayez d’utiliser l’annotation @ResponseBody sur votre gestionnaire d’exception. par exemple:

public @ResponseBody ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {... }
1
Otto Touzil

J'ai commencé votre application et fonctionne très bien.

Maven:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

La classe de contrôleur est:

@Controller
public class ValidationController {


    @GetMapping(value = "/validate/{itemId}")
    public @ResponseBody ResponseEntity<MyItem> validateItem(@PathVariable Long itemId) {
        if (itemId.equals(Long.valueOf(1))) {
            throw new ItemNotFoundException();
        }
        return new ResponseEntity<>(new MyItem(), HttpStatus.OK);
    }

    @ExceptionHandler(ItemNotFoundException.class)
    public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
        System.out.println("In CREEH::ItemNFE");
        ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
        ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
        return responseEntity;
    }
}

et le test:

@RunWith(SpringRunner.class)
@WebMvcTest(value = ValidationController.class, secure = false)

public class TestValidationController {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testExpectNotFound() throws Exception {
        mockMvc.perform(get("/validate/1"))
                .andExpect(status().isNotFound());
    }

    @Test
    public void testExpectFound() throws Exception {
        mockMvc.perform(get("/validate/2"))
                .andExpect(status().isOk());
    }
}

Êtes-vous sûr que l'URL que vous essayez d'utiliser avec RestTemplate est correct?

 String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";

Votre méthode get est @GetMapping (value = "/ validate/{itemId}" 

Si vous n'avez pas de mappage de demande au niveau du contrôleur, l'URL devrait être:

 http://localhost:8080/validate/1

Une autre différence est l'absence de @ResponseBody sur votre méthode de contrôleur.

0
Adina

Vous devez utiliser Custom Exception Handler pour résoudre votre cas. Ça ressemble à ça

@ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    public CustomResponseEntityExceptionHandler() {
        super();
    }
    // 404
    @ExceptionHandler(value = { EntityNotFoundException.class, ResourceNotFoundException.class })
    protected ResponseEntity<Object> handleNotFound(final RuntimeException ex, final WebRequest request) {      
        BaseResponse responseError = new BaseResponse(HttpStatus.NOT_FOUND.value(),HttpStatus.NOT_FOUND.name(),
                Constants.HttpStatusMsg.ERROR_NOT_FOUND);       
        logger.error(ex.getMessage());
        return handleExceptionInternal(ex, responseError, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
    }

}

Et votre code devrait lancer une exception, par exemple:

if (your_entity == null) {
    throw new EntityNotFoundException("said something");
}

Si vous obtenez ce cas ailleurs encore, il vous suffit de lancer une exception comme ci-dessus. Votre gestionnaire s'occupera du reste.

J'espère que cette aide.

0
David Pham