自定义异常处理之@ControllerAdvice
1 @ControllerAdvice的介绍:
在Spring3.2中,新增了@ControllerAdvice注解,可以用于定义@ExceptionHandler,@InitBinder,@ModelAttribute,然后与@RequestMapping配合使用。
2 @ControllerAdvice的使用:
2.1 如果我们想要完成一个自定义异常,我们需要使用@ControllerAdvice注解来实现一个全局异常处理类来处理我们的目标异常。代码实例:GlobalExceptionResolver.java
package com.test.exception;
import com.test.exception.CustomException;
import com.test.exception.message.MessageDecodingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* Global exception.
*/
@ControllerAdvice
public class GlobalExceptionResolver {
private static Logger logger = LoggerFactory.getLogger(GlobalExceptionResolver.class);
/**
* 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
* @param binder
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
}
/**
* 把值绑定到Model中,使全局@RequestMapping可以获取到该值
* @param model
*/
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("author", "Magical Sam");
}
/**
* System Exception
*/
@ExceptionHandler(value = Exception.class)
public Map<String, Object> handle(Exception e) {
logger.error("System Exception:", e);
Map<String, Object> errorMap = new HashMap<>();
return errorMap;
}
/**
* 自定义验证异常
*/
@ExceptionHandler(value = MessageDecodingException.class)
public Map<String, Object> handle(MessageDecodingException e) {
logger.error("Defined Message Decode Exception", e);
Map<String, Object> errorMap = new HashMap<>();
return errorMap;
}
/**
* Runtime Exception
*/
@ExceptionHandler(value = RuntimeException.class)
public Map<String, Object> handle(RuntimeException e) {
logger.error("Runtime Exception:", e);
Map<String, Object> errorMap = new HashMap<>();
return errorMap;
}
/**
* Custom
* CustomException
*/
@ExceptionHandler(value = CustomException.class)
public Map<String, Object> handle(CustomException e) {
logger.error("CustomException Exception:", e);
Map<String, Object> errorMap = new HashMap<>();
errorMap.put("errorCode", e.getErrorCode());
errorMap.put("errorMessage", e.getErrorMessage());
errorMap.put("parameter", e.getParameter());
return errorMap;
}
}
2.2 所谓自定义异常当然也需要自定义的异常来处理,GlobalExceptionResolver.java只是一个全局异常处理类,所以我们要编写一个自定义异常来提供处理。代码如下:CustomException.java,他继承了RuntimeException类:
package com.test.exception;
import com.test.exception.enum.Error;
import java.util.List;
import java.util.Set;
/**
* @ClassName CustomException
* @Description TODO
* @Author calm_encode
* @Date 12/6/2019 3:36 PM
* @Version 1.0
**/
public class CustomException extends RuntimeException {
private final String errorCode;
private final String errorMessage;
private final String parameter;
public CustomException(Throwable t) {
super(t);
this.errorCode = Error.SYSTEM_EXCEPTION.getErrorCode();
this.errorMessage = t.getMessage() + "" + t.getClass().getName();
this.parameter = null;
}
public CustomException(String errorCode, String errorMessage, String parameter) {
super();
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.parameter = parameter;
}
public CustomException(String errorCode) {
super();
this.errorCode = errorCode;
this.errorMessage = null;
this.parameter = null;
}
public CustomException(String errorCode, String errorMessage) {
super();
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.parameter = null;
}
public CustomException(Error error) {
super();
this.errorCode = error.getErrorCode();
this.errorMessage = error.getErrorMessage();
this.parameter = null;
}
public CustomException(Error error, String parameter) {
super();
this.errorCode = error.getErrorCode();
this.errorMessage = error.getErrorMessage();
this.parameter = parameter;
}
public String getErrorCode() {
return errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public String getParameter() {
return parameter;
}
@Override
public String toString() {
return "CustomException{" +
"errorCode='" + errorCode + '\'' +
", errorMessage='" + errorMessage + '\'' +
", parameter=" + parameter +
'}';
}
}
2.3 除上述代码实例提供的自定义异常处理所需的自定义异常之外,我们应该补充我们的错误类别实例,为自定义异常提供我们的目标异常。基于这个需求我们可以使用一下两步实现错误类别实例的创建。
2.3.1 编写一个自定义异常错误接口定义需要实现的方法:
package com.test.exception.enum;
import org.springframework.http.HttpStatus;
import java.io.Serializable;
public interface IError extends Serializable {
String getErrorCode();
String getErrorMessage();
HttpStatus getHttpStatus();
}
2.3.2 编写一个自定义异常错误接口实现类:
package com.test.exception.enum;
import org.springframework.http.HttpStatus;
/**
* @author Calm_encode
*/
public enum Error implements IError {
SYSTEM_EXCEPTION("SDK_Fabric_500","System Exception",HttpStatus.INTERNAL_SERVER_ERROR),
UNDEFINED_EXCEPTION("SDK_Fabric_400","Undefined Exception",HttpStatus.BAD_REQUEST),
CHAIN_CODE_EXCEPTION("Chain_code_500","Chain code Exception", HttpStatus.INTERNAL_SERVER_ERROR),
MISSING_MANDATORY_FIELD("SDK_Fabric_400", "Missing Mandatory Field", HttpStatus.BAD_REQUEST),
VALUE_NOT_ALLOWED("SDK_Fabric_400", "Value Not Allowed", HttpStatus.BAD_REQUEST),
BAD_REQUEST("SDK_Fabric_400","Exist empty value of the Parameters",HttpStatus.BAD_REQUEST),
INVALID_ID("SDK_Fabric_400","Don't exist the correlation data about the ID",HttpStatus.BAD_REQUEST);
private String errorCode;
private String errorMessage;
private HttpStatus httpStatus;
Error(){}
Error(String errorCode, String errorMessage, HttpStatus httpStatus){
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.httpStatus = httpStatus;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public void setHttpStatus(HttpStatus httpStatus) {
this.httpStatus = httpStatus;
}
@Override
public String getErrorCode() {
return errorCode;
}
@Override
public String getErrorMessage() {
return errorMessage;
}
@Override
public HttpStatus getHttpStatus() {
if (httpStatus == null) {
return HttpStatus.OK;
}
return httpStatus;
}
public static Error getErrorByCode(String errorCode){
Error error = Error.SYSTEM_EXCEPTION;
for (Error e : Error.values()){
if(e.errorCode.equals(errorCode)){
error = e;
break;
}
}
return error;
}
public static Error getErrorByMessage(String errorMessage){
Error error = Error.UNDEFINED_EXCEPTION;
error.setErrorMessage(errorMessage);
for(Error e : Error.values()) {
if(e.getErrorMessage().equals(errorMessage)){
error = e;
break;
}
}
return error;
}
}
到此处,基于@ControllerAdvice的自定义异常处理就写好了,现在@ControllerAdvice注解实现自定义异常处理的实现过程如下:
由于业务逻辑是在Service的实现类中处理,所以我们写好的业务需要抛出异常时,我们会相应的代码块中使用throw关键字来抛出我们想要的异常。
而Controller层时不会有任何逻辑代码的,它仅仅提供一个api的请求方法入口来设置请求的方法是post,get,put,delete还是 update,以及请求的放回方式是以页面返回使用@Contoller注解,还是以Restful风格返回相应的json数据,同时决定了客户端发送请求是需要传入的参数形式,以及参数个数。
所以正确的异常处理代码流程是:
1.Service层的业务逻辑产生异常,然后抛出异常到Controller层。
2.由于Spring boot 默认情况下会映射到 /error 进行异常处理,但是提示并不十分友好,一般只是提示SytemException的错误信息以及500的错误码,所以我们需要在Controller增加自定义异常处理来使得错误信息显示的更加详细。
3.使用@ControllerAdvice来实现自定义异常处理。同时@ExceptionHandler拦截了异常,我们可以通过该注解实现自定义异常处理。其中,@ExceptionHandler 配置的 value 指定需要拦截的异常类型。
而上述统一异常处理类GlobalExceptionResolver.java,自定义异常类CustomException.java,自定义异常错误方法实例接口IError.java以及自定义异常错误处理实例接口实现类Error.java为自定义异常处理提供了处理方法以及相应的错误方法参数实例。
产生异常的serivce层代码如下:
public TestDTO create(TestDTO testDTO) {
logger.info("Create : " + gson.toJson(testDTO));
if (ObjectUtils.isEmpty(testDTO)) {
throw new CustomException(Error.BAD_REQUEST);
}
try {
String result = clientApp.invoke(testName, contractName, create, campaignConfigurationDTO.getId(), gson.toJson(testDTO));
return gson.fromJson(result, TestDTO.class);
} catch (Exception e) {
/*logger.error(e.getMessage());
throw new CustomException(Error.CHAIN_CODE_EXCEPTION);*/
e.printStackTrace();
}
return null;
}
上述代码是将错误信息以json格式的数据返回,当我们将所有的异常信息使用json格式返回时,可以使用@RestControllerAdvice
来替换@ControllerAdvice与@ResponseBody注解的使用,效果相同。
如@RestController的作用效果同@Controller和@ResponseBody的效果相同的道理类同。
3.使用@Controller,@ContollerAdvice注解实现异常信息页面渲染并在浏览器中显示:
3.1 异常处理方法如下:
@ExceptionHandler(value = CustomException.class)
public ModelAndView handler(CustomException e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("code", e.getErrorCode());
modelAndView.addObject("msg", e.getErrorMessage());
modelAndView.addObject("parameter", e.getParameter());
return modelAndView;
}Error
3.2 在 templates 目录下,添加 error.ftl,使用freemarker 进行错误页面渲染:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>错误页面</title>
</head>
<body>
<h1>${code}</h1>
<h1>${msg}</h1>
<h1>${parameter}</h1>
</body>
</html>