敞开成长之旅!这是我参加「日新计划 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 项目后,将以下两个类添加到项目中:ApplicationExceptionApiErrorResponse

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"  
}

而在运用日志中,咱们也能够看到相应的过错信息:

SpringBoot中该如何处理异常?

定论

运用 Spring Boot 能够轻松完成自界说反常处理程序。运用RestControllerAdviceExceptionHandler注释,咱们在一个组件中完成了大局过错处理。

本文中供给的代码仅用于演示目的,您在完成自界说反常处理逻辑时应考虑运用程序的各个方面