敞开成长之旅!这是我参加「日新计划 12 月更文挑战」的第14天,点击检查活动详情
介绍
创立 API 的一项重要任务是回来可理解的过错音讯。Spring Boot 已经有预界说的过错音讯格局,但这种格局并不总是能够接受的,咱们的运用程序可能需要自界说格局。
在本教程中,咱们将配置 Spring Boot 的反常处理,以便咱们的后端运用程序以以下格局呼应过错音讯:
{
"guid": "DCF70619-01D8-42a9-97DC-6005F205361A",
"errorCode": "application-specific-error-code",
"message": "Error message",
"statusCode": 400,
"statusName": "BAD_REQUEST",
"path": "/some/path",
"method": "POST",
"timestamp": "2022-12-06"
}
格局说明:
- guid— 过错的仅有大局标识符,此字段对于在大型日志中搜索过错很有用。
- errorCode— 源自业务逻辑规矩的特定于运用程序的过错代码。
- message— 过错描述。
- statusCode— HTTP 状况代码。
- statusName— HTTP 状况代码的全名。
- path— 产生过错的资源的 URI。
- 办法——运用的 HTTP 办法。
- timestamp— 过错创立的时间戳。
履行
例如,让咱们创立一个用于处理城市列表的 REST API。
手动或运用 Spring Initializer 创立 Spring Boot 项目后,将以下两个类添加到项目中:ApplicationException和ApiErrorResponse。
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
@AllArgsConstructor
public class ApplicationException extends RuntimeException {
private final String errorCode;
private final String message;
private final HttpStatus httpStatus;
}
当运用程序产生反常时,将抛出 ApplicationException。
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ApiErrorResponse {
private final String guid;
private final String errorCode;
private final String message;
private final Integer statusCode;
private final String statusName;
private final String path;
private final String method;
private final LocalDateTime timestamp;
}
ApiErrorResponse是要序列化为 JSON 呼应的 DTO。
然后添加一个ApplicationExceptionHandler类来处理一切运用程序反常。
import io.github.sergiusac.exceptionhandling.response.ApiErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.UUID;
@Slf4j
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ApplicationException.class)
public ResponseEntity<?> handleApplicationException(
final ApplicationException exception, final HttpServletRequest request
) {
var guid = UUID.randomUUID().toString();
log.error(
String.format("Error GUID=%s; error message: %s", guid, exception.getMessage()),
exception
);
var response = new ApiErrorResponse(
guid,
exception.getErrorCode(),
exception.getMessage(),
exception.getHttpStatus().value(),
exception.getHttpStatus().name(),
request.getRequestURI(),
request.getMethod(),
LocalDateTime.now()
);
return new ResponseEntity<>(response, exception.getHttpStatus());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleUnknownException(
final Exception exception, final HttpServletRequest request
) {
var guid = UUID.randomUUID().toString();
log.error(
String.format("Error GUID=%s; error message: %s", guid, exception.getMessage()),
exception
);
var response = new ApiErrorResponse(
guid,
ErrorCodes.INTERNAL_ERROR,
"Internal server error",
HttpStatus.INTERNAL_SERVER_ERROR.value(),
HttpStatus.INTERNAL_SERVER_ERROR.name(),
request.getRequestURI(),
request.getMethod(),
LocalDateTime.now()
);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
咱们运用RestControllerAdvice注释创立一个大局处理运用程序中反常的 bean,并运用ExceptionHandler注释来指定反常。
handleApplicationException办法处理ApplicationException类的一切反常。此办法为反常生成一个 GUID,将反常写入日志,并将过错呼应发送回客户端。
handleUnknownException办法履行相同的操作,但用于一切其他反常 。
接下来,咱们创立CityService来处理城市列表。
import io.github.sergiusac.exceptionhandling.exception.ApplicationException;
import io.github.sergiusac.exceptionhandling.model.City;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.stream.Collectors;
@Service
public class CityService {
private final Map<Long, City> cities = new ConcurrentHashMap<>() {
{
put(1L, new City(1L, "Paris", LocalDateTime.now(), LocalDateTime.now()));
put(2L, new City(2L, "New-York", LocalDateTime.now(), LocalDateTime.now()));
put(3L, new City(3L, "Barcelona", LocalDateTime.now(), LocalDateTime.now()));
}
};
public List<City> getAllCities() {
return cities.values().stream().collect(Collectors.toUnmodifiableList());
}
public City getCityById(final Long id) {
var city = cities.get(id);
if (city == null) {
throw new ApplicationException(
"city-not-found",
String.format("City with id=%d not found", id),
HttpStatus.NOT_FOUND
);
}
return city;
}
}
该服务有一个内置的城市列表和两种检查城市列表的办法。此外,getCityById办法会抛出自界说ApplicationException以及所供给的信息,例如过错代码、音讯和 HTTP 状况。
接下来,咱们创立一个 REST 控制器。
import io.github.sergiusac.exceptionhandling.service.CityService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
@RequestMapping("/cities")
public class CityController {
private final CityService cityService;
@GetMapping
public ResponseEntity<?> getAllCities() {
return ResponseEntity.ok(cityService.getAllCities());
}
@GetMapping("/{id}")
public ResponseEntity<?> getCityById(@PathVariable final Long id) {
return ResponseEntity.ok(cityService.getCityById(id));
}
}
这个控制器只是供给了两个类似于CityService的办法。
编译并运转运用程序后,咱们应该看到以下成果:
GET http://localhost:8080/cities
[
{
"id": 1,
"cityName": "Paris",
"createdAt": "2022-12-06T22:06:18.6921738",
"updatedAt": "2022-12-06T22:06:18.6921738"
},
{
"id": 2,
"cityName": "New-York",
"createdAt": "2022-12-06T22:06:18.6921738",
"updatedAt": "2022-12-06T22:06:18.6921738"
},
{
"id": 3,
"cityName": "Barcelona",
"createdAt": "2022-12-06T22:06:18.6921738",
"updatedAt": "2022-12-06T22:06:18.6921738"
}
]
如咱们所见,第一个办法回来包含三个城市列表的正确呼应。但是当咱们尝试获取 ID 不知道的城市时,咱们会收到以下过错呼应:
GET http://localhost:8080/cities/4
{
"guid": "01913964-5777-4ec1-bd5e-392c5a5fecc9",
"errorCode": "city-not-found",
"message": "City with id=4 not found",
"statusCode": 404,
"statusName": "NOT_FOUND",
"path": "/cities/4",
"method": "GET",
"timestamp": "2022-12-06T22:10:37.8993481"
}
而在运用日志中,咱们也能够看到相应的过错信息:
定论
运用 Spring Boot 能够轻松完成自界说反常处理程序。运用RestControllerAdvice和ExceptionHandler注释,咱们在一个组件中完成了大局过错处理。
本文中供给的代码仅用于演示目的,您在完成自界说反常处理逻辑时应考虑运用程序的各个方面