1. Overview
1.概述
In this tutorial, we’re going to explore how to produce application/problem+json responses using the Problem Spring Web library. This library helps us to avoid repetitive tasks related to error handling.
在本教程中,我们将探讨如何产生application/problem+json响应 使用Problem Spring Web 库。这个库可以帮助我们避免与错误处理相关的重复性工作。
By integrating Problem Spring Web into our Spring Boot application, we can simplify the way we handle exceptions within our project and generate responses accordingly.
通过将问题Spring Web集成到我们的Spring Boot应用程序中,我们可以简化我们在项目中处理异常的方式并生成相应的响应。
2. The Problem Library
2.问题库
Problem is a small library with the purpose of standardizing the way Java-based Rest APIs express errors to their consumers.
问题是一个小型库,目的是使基于Java的Rest API向其消费者表达错误的方式标准化。
A Problem is an abstraction of any error we want to inform about. It contains handy information about the error. Let’s see the default representation of a Problem response:
一个Problem是我们想要通知的任何错误的抽象。它包含关于错误的便利信息。让我们看看Problem响应的默认表示。
{
"title": "Not Found",
"status": 404
}
In this case, the status code and the title are enough to describe the error. However, we can also add a detailed description of it:
在这种情况下,状态代码和标题就足以描述这个错误。然而,我们也可以添加一个详细的描述。
{
"title": "Service Unavailable",
"status": 503,
"detail": "Database not reachable"
}
We can also create custom Problem objects that adapt to our needs:
我们还可以创建自定义的Problem对象,以适应我们的需求。
Problem.builder()
.withType(URI.create("https://example.org/out-of-stock"))
.withTitle("Out of Stock")
.withStatus(BAD_REQUEST)
.withDetail("Item B00027Y5QG is no longer available")
.with("product", "B00027Y5QG")
.build();
In this tutorial we’ll focus on the Problem library implementation for Spring Boot projects.
在本教程中,我们将重点介绍Spring Boot项目的问题库实现。
3. Problem Spring Web Setup
问题Spring网设置
Since this is a Maven based project, let’s add the problem-spring-web dependency to the pom.xml:
由于这是一个基于Maven的项目,我们把problem-spring-web依赖关系添加到pom.xml。
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem-spring-web</artifactId>
<version>0.23.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.4.0</version>
</dependency>
We also need the spring-boot-starter-web and the spring-boot-starter-security dependencies. Spring Security is required from version 0.23.0 of problem-spring-web.
我们还需要spring-boot-starter-web和spring-boot-starter-security依赖。从0.23.0版本的problem-spring-web开始,Spring Security是必需的。
4. Basic Configuration
4.基本配置
As our first step, we need to disable the white label error page so we’ll able to see our custom error representation instead:
作为第一步,我们需要禁用白标错误页面,这样我们就能看到我们的自定义错误表述。
@EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)
Now, let’s register some of the required components in the ObjectMapper bean:
现在,让我们在ObjectMapperbean中注册一些必要的组件。
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper().registerModules(
new ProblemModule(),
new ConstraintViolationProblemModule());
}
After that, we need to add the following properties to the application.properties file:
之后,我们需要在application.properties文件中添加以下属性。
spring.resources.add-mappings=false
spring.mvc.throw-exception-if-no-handler-found=true
spring.http.encoding.force=true
And finally, we need to implement the ProblemHandling interface:
最后,我们需要实现ProblemHandling接口。
@ControllerAdvice
public class ExceptionHandler implements ProblemHandling {}
5. Advanced Configuration
5.高级配置
In addition to the basic configuration, we can also configure our project to handle security-related problems. The first step is to create a configuration class to enable the library integration with Spring Security:
除了基本配置外,我们还可以配置我们的项目来处理与安全有关的问题。第一步是创建一个配置类,使库与Spring Security集成。
@Configuration
@EnableWebSecurity
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration {
@Autowired
private SecurityProblemSupport problemSupport;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// Other security-related configuration
http.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport);
return http.build();
}
}
And finally, we need to create an exception handler for security-related exceptions:
最后,我们需要为安全相关的异常创建一个异常处理程序。
@ControllerAdvice
public class SecurityExceptionHandler implements SecurityAdviceTrait {}
6. The REST Controller
6.REST控制器
After configuring our application, we are ready to create a RESTful controller:
在配置好我们的应用程序后,我们准备创建一个RESTful控制器。
@RestController
@RequestMapping("/tasks")
public class ProblemDemoController {
private static final Map<Long, Task> MY_TASKS;
static {
MY_TASKS = new HashMap<>();
MY_TASKS.put(1L, new Task(1L, "My first task"));
MY_TASKS.put(2L, new Task(2L, "My second task"));
}
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public List<Task> getTasks() {
return new ArrayList<>(MY_TASKS.values());
}
@GetMapping(value = "/{id}",
produces = MediaType.APPLICATION_JSON_VALUE)
public Task getTasks(@PathVariable("id") Long taskId) {
if (MY_TASKS.containsKey(taskId)) {
return MY_TASKS.get(taskId);
} else {
throw new TaskNotFoundProblem(taskId);
}
}
@PutMapping("/{id}")
public void updateTask(@PathVariable("id") Long id) {
throw new UnsupportedOperationException();
}
@DeleteMapping("/{id}")
public void deleteTask(@PathVariable("id") Long id) {
throw new AccessDeniedException("You can't delete this task");
}
}
In this controller, we’re intentionally throwing some exceptions. Those exceptions will be converted into Problem objects automatically to produce an application/problem+json response with the details of the failure.
在这个控制器中,我们有意抛出一些异常。这些异常将被自动转换为Problem对象,以产生一个application/problem+json响应,其中包含故障的细节。
Now, let’s talk about the built-in advice traits and also how to create a custom Problem implementation.
现在,让我们来谈谈内置的建议特性,以及如何创建一个自定义的Problem实现。
7. Built-in Advice Traits
7.内置建议的特征
An advice trait is a small exception handler that catches exceptions and returns the proper problem object.
一个建议特质是一个小的异常处理程序,它可以捕捉异常并返回适当的问题对象。
There are built-in advice traits for common exceptions. Hence, we can use them by simply throwing the exception:
对于常见的异常,有内置的建议特质。因此,我们可以通过简单地抛出异常来使用它们。
throw new UnsupportedOperationException();
As a result, we’ll get the response:
结果是,我们会得到回应。
{
"title": "Not Implemented",
"status": 501
}
Since we configured the integration with Spring Security as well, we’re able to throw security-related exceptions:
由于我们也配置了与Spring Security的集成,我们能够抛出与安全有关的异常。
throw new AccessDeniedException("You can't delete this task");
And get the proper response:
并得到适当的回应。
{
"title": "Forbidden",
"status": 403,
"detail": "You can't delete this task"
}
8. Creating a Custom Problem
8.创建一个自定义的问题
It’s possible to create a custom implementation of a Problem. We just need to extend the AbstractThrowableProblem class:
我们可以创建一个自定义的Problem的实现。我们只需要扩展AbstractThrowableProblem类。
public class TaskNotFoundProblem extends AbstractThrowableProblem {
private static final URI TYPE
= URI.create("https://example.org/not-found");
public TaskNotFoundProblem(Long taskId) {
super(
TYPE,
"Not found",
Status.NOT_FOUND,
String.format("Task '%s' not found", taskId));
}
}
And we can throw our custom problem as follows:
而我们可以按以下方式抛出我们的自定义问题。
if (MY_TASKS.containsKey(taskId)) {
return MY_TASKS.get(taskId);
} else {
throw new TaskNotFoundProblem(taskId);
}
As a result of throwing the TaskNotFoundProblem problem, we’ll get:
作为抛出TaskNotFoundProblem问题的结果,我们会得到。
{
"type": "https://example.org/not-found",
"title": "Not found",
"status": 404,
"detail": "Task '3' not found"
}
9. Dealing with Stack Traces
9.处理堆栈痕迹
If we want to include stack traces within the response, we need to configure our ProblemModule accordingly:
如果我们想在响应中包括堆栈跟踪,我们需要相应地配置我们的ProblemModule。
ObjectMapper mapper = new ObjectMapper()
.registerModule(new ProblemModule().withStackTraces());
The causal chain of causes is disabled by default, but we can easily enable it by overriding the behavior:
默认情况下,原因的因果链是禁用的,但我们可以通过覆盖行为轻松地启用它。
@ControllerAdvice
class ExceptionHandling implements ProblemHandling {
@Override
public boolean isCausalChainsEnabled() {
return true;
}
}
After enabling both features we’ll get a response similar to this one:
启用这两个功能后,我们会得到一个与此类似的回应。
{
"title": "Internal Server Error",
"status": 500,
"detail": "Illegal State",
"stacktrace": [
"org.example.ExampleRestController
.newIllegalState(ExampleRestController.java:96)",
"org.example.ExampleRestController
.nestedThrowable(ExampleRestController.java:91)"
],
"cause": {
"title": "Internal Server Error",
"status": 500,
"detail": "Illegal Argument",
"stacktrace": [
"org.example.ExampleRestController
.newIllegalArgument(ExampleRestController.java:100)",
"org.example.ExampleRestController
.nestedThrowable(ExampleRestController.java:88)"
],
"cause": {
// ....
}
}
}
10. Conclusion
10.结语
In this article, we explored how to use the Problem Spring Web library to create responses with the errors’ details using an application/problem+json response. We also learned how to configure the library in our Spring Boot application and create a custom implementation of a Problem object.
在这篇文章中,我们探讨了如何使用Problem Spring Web库,使用application/problem+json响应创建包含错误细节的响应。我们还学习了如何在Spring Boot应用程序中配置该库,并创建一个Problem对象的自定义实现。
The implementation of this guide can be found in the GitHub project – this is a Maven based project, so it should be easy to import and run it as is.
本指南的实现可以在GitHub项目中找到–这是一个基于Maven的项目,所以按原样导入和运行应该很容易。