# 异常处理

对于业务异常情况（数据找不到、找到子记录等），业务代码中只需封装异常并抛出，最后在请求处理层通过切面的方式进行统一的异常处理，返回统一的异常结构。

## 异常封装

后端返回的异常信息必需的元素是错误信息（message）。异常的错误编码（code）可以根据业务需要进行细化（一般来说使用HTTP status code即可满足要求），某些情况下，异常可能会携带发生异常的具体数据。

{% code title="ErrorResult.java" %}

```java
package com.example.demo;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class ErrorResult {
    private int code;
    private String message;
    private Object data;
}
```

{% endcode %}

{% hint style="info" %}
使用lombok提供的`@Builder`注解可以很方便的实现建造者模式。
{% endhint %}

## 异常定义

业务性质的异常，直接使用`BizException`即可。如果开发人员希望简化某些结构化异常的编码，可以继承`BizException`定义业务异常。

以找不到指定的数据记录为例：

{% code title="ResourceNotFoundException.java" %}

```java
package com.example.demo.exception;

public class ResourceNotFoundException extends BizException {
    // 记录业务主键
    private final Object pk;

    public ResourceNotFoundException(String entityName, Object pk) {
        super("找不到ID为" + pk + "的" + entityName);
        this.pk = pk;
    }
    
    public Object getPk() {
        return pk;
    }
}
```

{% endcode %}

* 使用自定义异常：`throw new ResourceNotFoundException("航班", 1);`
* 不使用自定义异常：`throw new BizException("找不到ID为1的航班");`

{% hint style="info" %}
业务异常的定义是灵活的，异常中的内容和构造方法可以根据需要定义。
{% endhint %}

## 异常抛出

业务代码中，根据需要抛出自定义的异常。

{% code title="FooController.java" %}

```java
package com.example.demo.controller;

import com.example.demo.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

@RestController
@RequestMapping("/foos")
@Api(tags = "Foo CRUD服务")
public class FooController {

    @Autowired
    private FooService fooService;

    @GetMapping("/{id}")
    @ApiOperation("根据ID查询结果")
    public FooDto findById(@PathVariable("id") Long id) {
        Optional<FooDto> optional = fooService.findById(id);
        if (optional.isPresent()) {
            FooDto foo = optional.get();
            if (foo.isDisabled()) {
                throw new BizException("Foo is disabled");
            }
            return foo;
        } else {
            throw new ResourceNotFoundException("Foo", id);
        }
    }
}
```

{% endcode %}

## 异常处理

{% hint style="warning" %}
此部分内容仅作为原理说明，实际开发需要使用统一异常处理的共用组件
{% endhint %}

开发者需要使用`@RestControllerAdvice`注解定义统一的异常处理类，针对需要处理的异常（可能是自定义业务异常，也有可能是JPA定义的`PersistenceException`），使用`@ExceptionHandler`注解进行全局的捕获和处理。

{% code title="GlobalExceptionHandler.java" %}

```java
package com.example.demo;

import com.example.demo.exception.ResourceNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestControllerAdvice
public class GlobalExceptionHandler {
    // 可以根据需要定义本系统的错误码
    public static final int RC_RESOURCE_NOT_FOUND = 40001;
    public static final int RC_UNKNOWN_ERROR = 50000;

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(ResourceNotFoundException.class)
    public ErrorResult handleResourceNotFoundException(ResourceNotFoundException e) {
        return ErrorResult.builder()
                .code(RC_RESOURCE_NOT_FOUND) // 可选
                .message(e.getMessage())
                .data(e.getPk())
                .build();
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public ErrorResult handleOtherException(Exception e) {
        return ErrorResult.builder()
                .code(RC_UNKNOWN_ERROR) // 可选
                .message(e.getMessage())
                .build();
    }
}

```

{% endcode %}

{% hint style="warning" %}
异常处理一定要指定`@ResponseStatus`，避免返回200（正常）的状态码。
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://taocares.gitbook.io/development-guide/kai-fa-zhi-nan/yi-chang-chu-li.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
