我使用Spring Boot编写REST服务。
当操作成功并且相应地失败时,我需要返回不同的实体。 Spring中的ResponseEntity
按类型T
进行参数化。我知道我可以省略类型并仅返回ResponseEntity
,但在尝试使用Java 8 Optional
的{{1}}链创建响应时,这还不够:
orElse
由于Java编译器的类型推断public ResponseEntity getDashboard(String user, UUID uuid) {
Optional<Dashboard> dashboard = dashboardService.getDashboard( user, uuid );
// this gives unchecked assignment: 'org.springframework.http.ResponseEntity'
// to 'org.springframework.http.ResponseEntity<my.package.SomeClass>'
return dashboard
.map( ResponseEntity::ok )
.orElse( createNotFoundResponse( uuid, "No such object" ) );
}
public static <T> ResponseEntity createNotFoundResp( T entity, String message ) {
ResponseMessage<T> responseMessage = new ResponseMessage<>( message, entity );
return ResponseEntity.status( HttpStatus.NOT_FOUND ).body( responseMessage );
}
子句应该返回与可选项不为空时相同的类型,即orElse
而不是ResponseEntity<Dashboard>
。我试图通过提供不同的返回路径来颠覆这个问题:
ResponseEntity<ResponseMessage>
...但随后Intellij突出显示if ( dashboard.isPresent() ) {
return ResponseEntity.ok( dashboard.get() );
} else {
return createNotFoundResponse( uuid, "No such object" );
}
部分,并大声说这个块可以简化为上面的那个(导致未经检查的警告)。
有没有办法在没有任何编译器警告和dashboard.isPresent()
注释的情况下干净地编写此代码?
答案 0 :(得分:2)
有没有办法在没有任何编译器警告和
@SuppressUnchecked
注释的情况下干净地编写此代码?
在这种情况下,我不能摆脱编译器警告。一个可能的干净解决方案(至少没有编译器警告)拒绝Optional.map
的想法,而赞成简单的if
/ else
或?:
驱动的策略不可用虽然有流畅的界面。
static <T, U> ResponseEntity<?> okOrNotFound(final Optional<T> optional, final Supplier<? extends U> orElse) {
return okOrNotFound(optional, "Not found", orElse);
}
static <T, U> ResponseEntity<?> okOrNotFound(final Optional<T> optional, final String message, final Supplier<? extends U> orElse) {
return optional.isPresent()
? status(OK).body(optional.get())
: status(NOT_FOUND).body(new NotFound<>(orElse.get(), message));
}
@RequestMapping(method = GET, value = "/")
ResponseEntity<?> get(
@RequestParam("user") final String user,
@RequestParam("uuid") final UUID uuid
) {
final Optional<Dashboard> dashboard = dashboardService.getDashboard(user, uuid);
return okOrNotFound(dashboard, () -> uuid);
}
注意orElse
并不是您想要的:orElseGet
是懒惰的,只有在给定的可选值不存在时才会调用其供应商。
然而,Spring提供了一种更好的方法来完成你所需要的东西,我相信一种更干净的方式来做这样的事情。请查看为此目的而设计的controller advices。
// I would prefer a checked exception having a super class like ContractException
// However you can superclass this one into your custom super exception to serve various purposes and contain exception-related data to be de-structured below
final class NotFoundException
extends NoSuchElementException {
private final Object entity;
private NotFoundException(final Object entity) {
this.entity = entity;
}
static NotFoundException notFoundException(final Object entity) {
return new NotFoundException(entity);
}
Object getEntity() {
return entity;
}
}
现在REST控制器方法变为:
@RequestMapping(method = GET, value = "/")
Dashboard get(
@RequestParam("user") final String user,
@RequestParam("uuid") final UUID uuid
) {
return dashboardService.getDashboard(user, uuid)
.orElseThrow(() -> notFoundException(uuid));
}
Spring非常聪明,可以将对象转换为status(OK).body(T)
本身,因此我们只是抛出一个包含我们感兴趣的单个对象的异常。接下来,示例控制器异常建议可能如下所示:
@ControllerAdvice
final class ExceptionControllerAdvice {
@ExceptionHandler(NotFoundException.class)
ResponseEntity<NotFound<?>> acceptNotFoundException(final NotFoundException ex) {
return status(NOT_FOUND).body(notFound(ex));
}
}
其中notFound()
方法的实现如下:
static NotFound<?> notFound(final NotFoundException ex) {
return notFound(ex, "Not found");
}
static NotFound<?> notFound(final NotFoundException ex, final String message) {
return new NotFound<>(ex.getEntity(), message);
}
对于我的尖峰项目,提供以下结果:
{"description":"dashboard owned by owner"}
{"entity":"00000000-0000-0000-0000-000000000000","message":"Not found"}