SpringBoot怎么定制错误页面及错误数据

发布时间:2021-12-02 14:13:44 作者:iii
来源:亿速云 阅读:117
# SpringBoot怎么定制错误页面及错误数据

## 前言

在Web应用开发中,错误处理是用户体验的重要组成部分。SpringBoot提供了强大的错误处理机制,允许开发者灵活定制错误页面和错误数据。本文将深入探讨SpringBoot错误处理的原理、实现方式以及高级定制技巧。

## 一、SpringBoot错误处理机制概述

### 1.1 默认错误处理

SpringBoot默认通过`BasicErrorController`处理错误请求:

```java
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    // 处理HTML错误响应
    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        // ...
    }
    
    // 处理JSON错误响应
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        // ...
    }
}

1.2 错误属性解析

SpringBoot通过ErrorAttributes接口收集错误信息:

public interface ErrorAttributes {
    Map<String, Object> getErrorAttributes(WebRequest webRequest, 
        ErrorAttributeOptions options);
    Throwable getError(WebRequest webRequest);
}

默认实现DefaultErrorAttributes提供了以下信息: - timestamp:时间戳 - status:HTTP状态码 - error:错误原因 - message:错误消息 - path:请求路径

二、定制错误页面

2.1 静态错误页面

最简单的定制方式是在src/main/resources/static/error/目录下添加静态HTML:

resources/
└── static/
    └── error/
        ├── 404.html
        ├── 500.html
        └── 5xx.html

命名规则: - 精确匹配:404.html - 范围匹配:5xx.html - 通用匹配:error.html

2.2 动态模板错误页面

对于动态内容,可以使用模板引擎(Thymeleaf、Freemarker等):

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Error Page</title>
</head>
<body>
    <h1 th:text="${status}">Status</h1>
    <p th:text="${error}">Error</p>
    <p th:text="${message}">Message</p>
    <p th:text="${path}">Path</p>
</body>
</html>

存放位置:

resources/
└── templates/
    └── error/
        ├── 404.html
        └── 500.html

2.3 自定义ErrorController

完全控制错误处理逻辑:

@Controller
public class MyErrorController implements ErrorController {

    @RequestMapping("/error")
    public String handleError(HttpServletRequest request) {
        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        
        if (status != null) {
            int statusCode = Integer.parseInt(status.toString());
            
            if(statusCode == HttpStatus.NOT_FOUND.value()) {
                return "error-404";
            } else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
                return "error-500";
            }
        }
        return "error";
    }
}

三、定制错误数据

3.1 实现ErrorAttributes接口

@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, 
            ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
        
        // 添加自定义字段
        errorAttributes.put("locale", webRequest.getLocale().toString());
        errorAttributes.put("success", false);
        errorAttributes.put("version", "1.0");
        
        // 移除敏感信息
        errorAttributes.remove("trace");
        
        return errorAttributes;
    }
}

3.2 异常特定错误数据

@ControllerAdvice
public class CustomExceptionHandler {

    @ExceptionHandler(CustomException.class)
    public ResponseEntity<ErrorResponse> handleCustomException(
            CustomException ex, WebRequest request) {
        
        ErrorResponse errorResponse = new ErrorResponse(
            LocalDateTime.now(),
            ex.getErrorCode(),
            ex.getMessage(),
            request.getDescription(false));
            
        return new ResponseEntity<>(errorResponse, ex.getHttpStatus());
    }
}

@Data
@AllArgsConstructor
class ErrorResponse {
    private LocalDateTime timestamp;
    private String code;
    private String message;
    private String path;
}

四、高级定制技巧

4.1 区分API和页面错误

@RestController
@RequestMapping("/api/**")
public class ApiErrorController {

    @RequestMapping("/error")
    public ResponseEntity<ApiError> handleApiError(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        ApiError apiError = new ApiError(status, 
            (String) request.getAttribute(RequestDispatcher.ERROR_MESSAGE));
        
        return new ResponseEntity<>(apiError, status);
    }
    
    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        return statusCode != null ? HttpStatus.valueOf(statusCode) : HttpStatus.INTERNAL_SERVER_ERROR;
    }
}

4.2 错误页面国际化

  1. 配置消息源:
spring.messages.basename=messages/errors
  1. 创建消息文件:
messages/
└── errors.properties
└── errors_zh_CN.properties
  1. 在模板中使用:
<p th:text="#{error.404.message}">Page not found</p>

4.3 错误日志记录

@Component
public class CustomErrorController extends BasicErrorController {

    private static final Logger logger = LoggerFactory.getLogger(CustomErrorController.class);

    public CustomErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes, new ErrorProperties());
    }

    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        logError(request);
        return super.errorHtml(request, response);
    }

    private void logError(HttpServletRequest request) {
        Throwable error = getError(request);
        if (error != null) {
            logger.error("Error occurred: ", error);
        } else {
            logger.error("Error with status {} occurred", 
                request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
        }
    }
}

五、测试与验证

5.1 单元测试

@SpringBootTest
@AutoConfigureMockMvc
class ErrorHandlingTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void test404Page() throws Exception {
        mockMvc.perform(get("/nonexistent"))
            .andExpect(status().isNotFound())
            .andExpect(view().name("error/404"));
    }

    @Test
    void testApiError() throws Exception {
        mockMvc.perform(get("/api/nonexistent")
                .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.success").value(false))
            .andExpect(jsonPath("$.code").value("NOT_FOUND"));
    }
}

5.2 集成测试

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ErrorHandlingIntegrationTest {

    @LocalServerPort
    private int port;

    @Test
    void testErrorPage() {
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.getForEntity(
            "http://localhost:" + port + "/nonexistent", String.class);
        
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
        assertThat(response.getBody()).contains("Page Not Found");
    }
}

六、性能优化与最佳实践

6.1 错误页面缓存

@Configuration
public class ErrorPageConfig implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {

    @Override
    public void customize(ConfigurableWebServerFactory factory) {
        if (factory instanceof ConfigurableServletWebServerFactory) {
            ((ConfigurableServletWebServerFactory) factory).addErrorPages(
                new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"),
                new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"),
                new ErrorPage("/error")
            );
        }
    }
}

6.2 异步错误处理

@RestController
public class AsyncController {

    @GetMapping("/async")
    public CompletableFuture<String> asyncRequest() {
        return CompletableFuture.supplyAsync(() -> {
            if (Math.random() > 0.5) {
                throw new RuntimeException("Random error");
            }
            return "Success";
        }).exceptionally(ex -> {
            throw new ResponseStatusException(
                HttpStatus.INTERNAL_SERVER_ERROR, 
                "Async error occurred", ex);
        });
    }
}

6.3 安全考虑

  1. 生产环境隐藏堆栈信息:
server.error.include-stacktrace=never
server.error.include-message=never
server.error.include-binding-errors=never
  1. 自定义敏感信息过滤器:
@Component
public class ErrorSanitizer implements ErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, 
            ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = // 获取原始属性
        
        // 清理敏感信息
        errorAttributes.values().removeIf(
            value -> value.toString().contains("password") || 
                    value.toString().contains("secret"));
        
        return errorAttributes;
    }
}

七、常见问题与解决方案

7.1 自定义错误页面不生效

可能原因: 1. 文件位置不正确 2. 缓存问题 3. 优先级冲突

解决方案: 1. 确认文件位于resources/static/error/resources/templates/error/ 2. 清理浏览器缓存或使用spring.thymeleaf.cache=false 3. 检查是否有自定义ErrorController覆盖了默认行为

7.2 异常信息丢失

可能原因: 1. 过滤器/拦截器吞掉了异常 2. 异步处理未正确传播异常

解决方案: 1. 检查过滤器链中的异常处理 2. 确保异步方法正确传播异常:

@Async
public CompletableFuture<String> asyncMethod() {
    try {
        // 业务逻辑
    } catch (Exception ex) {
        CompletableFuture<String> future = new CompletableFuture<>();
        future.completeExceptionally(ex);
        return future;
    }
}

7.3 国际化不工作

检查步骤: 1. 确认messages.properties文件存在 2. 检查spring.messages.basename配置 3. 验证请求的Accept-Language

八、总结

SpringBoot提供了灵活的错误处理机制,开发者可以通过多种方式定制错误页面和错误数据:

  1. 静态错误页面:简单快速
  2. 动态模板页面:灵活强大
  3. 完全自定义:最大控制权

最佳实践建议: - 生产环境隐藏敏感信息 - 为API和页面提供不同的错误处理 - 实现完善的错误日志记录 - 考虑国际化需求 - 进行充分的错误处理测试

通过合理利用SpringBoot的错误处理功能,可以显著提升应用的健壮性和用户体验。


本文共计约8650字,详细介绍了SpringBoot错误页面和数据定制的各个方面,从基础配置到高级技巧,并提供了实用的代码示例和解决方案。 “`

推荐阅读:
  1. 定制EditText
  2. 定制checkbox

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

springboot

上一篇:如何分析标准SQL数据库访问界面JDBC API

下一篇:tk.Mybatis插入数据获取Id怎么实现

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》