您好,登录后才能下订单哦!
# 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) {
        // ...
    }
}
SpringBoot通过ErrorAttributes接口收集错误信息:
public interface ErrorAttributes {
    Map<String, Object> getErrorAttributes(WebRequest webRequest, 
        ErrorAttributeOptions options);
    Throwable getError(WebRequest webRequest);
}
默认实现DefaultErrorAttributes提供了以下信息:
- timestamp:时间戳
- status:HTTP状态码
- error:错误原因
- message:错误消息
- path:请求路径
最简单的定制方式是在src/main/resources/static/error/目录下添加静态HTML:
resources/
└── static/
    └── error/
        ├── 404.html
        ├── 500.html
        └── 5xx.html
命名规则:
- 精确匹配:404.html
- 范围匹配:5xx.html
- 通用匹配:error.html
对于动态内容,可以使用模板引擎(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
完全控制错误处理逻辑:
@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";
    }
}
@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;
    }
}
@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;
}
@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;
    }
}
spring.messages.basename=messages/errors
messages/
└── errors.properties
└── errors_zh_CN.properties
<p th:text="#{error.404.message}">Page not found</p>
@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));
        }
    }
}
@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"));
    }
}
@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");
    }
}
@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")
            );
        }
    }
}
@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);
        });
    }
}
server.error.include-stacktrace=never
server.error.include-message=never
server.error.include-binding-errors=never
@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;
    }
}
可能原因: 1. 文件位置不正确 2. 缓存问题 3. 优先级冲突
解决方案:
1. 确认文件位于resources/static/error/或resources/templates/error/
2. 清理浏览器缓存或使用spring.thymeleaf.cache=false
3. 检查是否有自定义ErrorController覆盖了默认行为
可能原因: 1. 过滤器/拦截器吞掉了异常 2. 异步处理未正确传播异常
解决方案: 1. 检查过滤器链中的异常处理 2. 确保异步方法正确传播异常:
@Async
public CompletableFuture<String> asyncMethod() {
    try {
        // 业务逻辑
    } catch (Exception ex) {
        CompletableFuture<String> future = new CompletableFuture<>();
        future.completeExceptionally(ex);
        return future;
    }
}
检查步骤:
1. 确认messages.properties文件存在
2. 检查spring.messages.basename配置
3. 验证请求的Accept-Language头
SpringBoot提供了灵活的错误处理机制,开发者可以通过多种方式定制错误页面和错误数据:
最佳实践建议: - 生产环境隐藏敏感信息 - 为API和页面提供不同的错误处理 - 实现完善的错误日志记录 - 考虑国际化需求 - 进行充分的错误处理测试
通过合理利用SpringBoot的错误处理功能,可以显著提升应用的健壮性和用户体验。
本文共计约8650字,详细介绍了SpringBoot错误页面和数据定制的各个方面,从基础配置到高级技巧,并提供了实用的代码示例和解决方案。 “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。