Spring REST如何统一错误处理呢?

乔欣 Spring 发布时间:2023-02-05 21:16:12 阅读数:3181 1
我们日常开发中,经常看见一个后端系统返回的错误处理都具有统一的样式,那么这种统一错误处理是如何实现的呢?
下文笔者将一一道来,如下所示
SpringBoot中
    /error映射提供一个BasicErrorController控制器
        用于处理所有错误
     并提供getErrorAttributes可生成带有错误信息,HTTP状态码和异常消息的JSON响应块

BasicErrorController.java源码

package org.springframework.boot.autoconfigure.web.servlet.error;
 
//...
 
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
 
	//...
 
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
	Map<String, Object> body = getErrorAttributes(request,
			isIncludeStackTrace(request, MediaType.ALL));
	HttpStatus status = getStatus(request);
	return new ResponseEntity<>(body, status);
}
SpringBoot生成自定义异常的示例
       使用@ControllerAdvice处理自定义异常

自定义异常示例

package com.java265.error;
 
public class UserNotFoundException extends RuntimeException {
 
    public UserNotFoundException(Long id) {
        super("User id not found : " + id);
    }
 
}

当未找到UserId时,可使用UserNotFoundException异常

UserController.java

package com.java265;
 
//...
 
@RestController
public class UserController {
 
    @Autowired
    private UserRepository repository;
	
    // Find
    @GetMapping("/Users/{id}")
    User findOne(@PathVariable Long id) {
        return repository.findById(id)
                .orElseThrow(() -> new UserNotFoundException(id));
    }
 
	//...
}


//此代码会返回http代码500错误
//当未找到userId,最合理的情况应该返回404错误而非500错误
package com.java265.error;
 
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
 
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.list;
import java.util.Map;
import java.util.stream.Collectors;
 
@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
 
    // Let Spring BasicErrorController handle the exception, we just override the status code
    @ExceptionHandler(UserNotFoundException.class)
    public void springHandleNotFound(HttpServletResponse response) throws IOException {
        response.sendError(HttpStatus.NOT_FOUND.value());
    }
 
	//...
}

//返回404 
curl localhost:8080/Users/5
{
	"timestamp":"2022-02-5T21:11:18.360+0000",
	"status":404,
	"error":"Not Found",
	"message":"User id not found : 5",
	"path":"/Users/5"
}

自定义json返回信息

 
package com.java265.error;
 
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
public class CustomErrorResponse {
 
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime timestamp;
    private int status;
    private String error;
 
    //...getters setters
}
package com.java265.error;
 
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
 
import java.time.LocalDateTime;
 
@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
 
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<CustomErrorResponse> customHandleNotFound(Exception ex, WebRequest request) {
 
        CustomErrorResponse errors = new CustomErrorResponse();
        errors.setTimestamp(LocalDateTime.now());
        errors.setError(ex.getMessage());
        errors.setStatus(HttpStatus.NOT_FOUND.value());
 
        return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);
 
    }
 
	//...
}

Terminal
curl localhost:8080/Users/5
{
	"timestamp":"2022-02-5 21:11:19",
	"status":404,
	"error":"User id not found : 5"
}

JSR 303验证错误

Spring @valid验证错误
它将抛出handleMethodArgumentNotValid
package com.java265.error;
 
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
 
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
 
@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
 
    //...
 
    // @Validate For Validating Path Variables and Request Parameters
    @ExceptionHandler(ConstraintViolationException.class)
    public void constraintViolationException(HttpServletResponse response) throws IOException {
        response.sendError(HttpStatus.BAD_REQUEST.value());
    }
 
    // error handle for @Valid
    @Override
    protected ResponseEntity<Object>
    handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                 HttpHeaders headers,
                                 HttpStatus status, WebRequest request) {
 
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("timestamp", new Date());
        body.put("status", status.value());
 
        //Get all fields errors
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(x -> x.getDefaultMessage())
                .collect(Collectors.toList());
 
        body.put("errors", errors);
 
        return new ResponseEntity<>(body, headers, status);
 
    }
}

ResponseEntityExceptionHandler

ResponseEntityExceptionHandler.java

package org.springframework.web.servlet.mvc.method.annotation;
 
//...
public abstract class ResponseEntityExceptionHandler {
 
	@ExceptionHandler({
			HttpRequestMethodNotSupportedException.class,
			HttpMediaTypeNotSupportedException.class,
			HttpMediaTypeNotAcceptableException.class,
			MissingPathVariableException.class,
			MissingServletRequestParameterException.class,
			ServletRequestBindingException.class,
			ConversionNotSupportedException.class,
			TypeMismatchException.class,
			HttpMessageNotReadableException.class,
			HttpMessageNotWritableException.class,
			MethodArgumentNotValidException.class,
			MissingServletRequestPartException.class,
			BindException.class,
			NoHandlerFoundException.class,
			AsyncRequestTimeoutException.class
		})
	@Nullable
	public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
		HttpHeaders headers = new HttpHeaders();
 
		if (ex instanceof HttpRequestMethodNotSupportedException) {
			HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
			return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
		}
		else if (ex instanceof HttpMediaTypeNotSupportedException) {
			HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
			return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
		}
		//...
	}
 
	//...
 
}

DefaultErrorAttributes

覆盖所有异常的默认JSON错误响应
    创建一个bean并扩展DefaultErrorAttributes
package com.java265.error;
 
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
 
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
 
@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
 
    private static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
 
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
 
        //timestamp时间格式化
        Object timestamp = errorAttributes.get("timestamp");
        if (timestamp == null) {
            errorAttributes.put("timestamp", dateFormat.format(new Date()));
        } else {
            errorAttributes.put("timestamp", dateFormat.format((Date) timestamp));
        }
 
        //插入键
        errorAttributes.put("version", "2.4");
        return errorAttributes;
    }
}
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

本文链接: https://www.Java265.com/JavaFramework/Spring/202302/5659.html

最近发表

热门文章

好文推荐

Java265.com

https://www.java265.com

站长统计|粤ICP备14097017号-3

Powered By Java265.com信息维护小组

使用手机扫描二维码

关注我们看更多资讯

java爱好者