枚举类定义异常类型以及注解@RestControllerAdvice用法途

https://blog.csdn.net/m0_51620667/article/details/127013347

一、@RestControllerAdvice是什么
@RestControllerAdvice是一个组合注解,由@ControllerAdvice、@ResponseBody组成,而@ControllerAdvice继承了@Component,因此@RestControllerAdvice本质上是个Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。

二、@RestControllerAdvice的特点
通过@ControllerAdvice注解可以将对于控制器的全局配置放在同一个位置。
注解了@RestControllerAdvice的类的方法可以使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上。
@RestControllerAdvice注解将作用在所有注解了@RequestMapping的控制器的方法上。
@ExceptionHandler:用于指定异常处理方法。当与@RestControllerAdvice配合使用时,用于全局处理控制器里的异常。
@InitBinder:用来设置WebDataBinder,用于自动绑定前台请求参数到Model中。
@ModelAttribute:本来作用是绑定键值对到Model中,当与@ControllerAdvice配合使用时,可以让全局的@RequestMapping都能获得在此处设置的键值对
@ControllerAdvice  
public class GlobalController{  
     
    //(1)全局数据绑定
    //应用到所有@RequestMapping注解方法  
    //此处将键值对添加到全局,注解了@RequestMapping的方法都可以获得此键值对  
    @ModelAttribute 
    public void addUser(Model model) {   
        model.addAttribute("msg", "此处将键值对添加到全局,注解了@RequestMapping的方法都可以获得此键值对");  
    }    
    //(2)全局数据预处理
    //应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器  
    //用来设置WebDataBinder  
    @InitBinder("user")
    public void initBinder(WebDataBinder binder) {
    }    
    
    // (3)全局异常处理
    //应用到所有@RequestMapping注解的方法,在其抛出Exception异常时执行  
    //定义全局异常处理,value属性可以过滤拦截指定异常,此处拦截所有的Exception  
    @ExceptionHandler(Exception.class)    
    public String handleException(Exception e) {    
        return "error";
    }    
}  


@ControllerAdvice可以指定 Controller 范围

basePackages: 指定一个或多个包,这些包及其子包下的所有 Controller 都被该 @ControllerAdvice 管理
@RestControllerAdvice(basePackages={"top.onething"})
@Slf4j
public class ExceptionHandlerAdvice {    
    @ExceptionHandler(Exception.class)    
    public String handleException(Exception e) {    
        return "error";
    }   


basePackageClasses: 是 basePackages 的一种变形,指定一个或多个 Controller 类,这些类所属的包及其子包下的所有 Controller 都被该 @ControllerAdvice 管理
@RestControllerAdvice(basePackageClasses={TestController.class})
@Slf4j
public class ExceptionHandlerAdvice {
    @ExceptionHandler(Exception.class)    
    public String handleException(Exception e) {    
        return "error";
    } 
}  
1
2
3
4
5
6
7
8
assignableTypes: 指定一个或多个 Controller 类,这些类被该 @ControllerAdvice 管理
@RestControllerAdvice(assignableTypes={TestController.class})
@Slf4j
public class ExceptionHandlerAdvice {
    @ExceptionHandler(Exception.class)    
    public String handleException(Exception e) {    
        return "error";
    } 
}  

annotations: 指定一个或多个注解,被这些注解所标记的 Controller 会被该 @ControllerAdvice 管理
@ControllerAdvice(annotations = {TestAnnotation.class})
@Slf4j
public class ExceptionHandlerAdvice {
    @ExceptionHandler(Exception.class)    
    public String handleException(Exception e) {    
        return "error";
    } 


三、@ExceptionHandler
我们可以搭配@ResponseStatus:可以将某种异常映射为HTTP状态码

首先需要为自己的系统设计一个自定义的异常类,通过它来传递状态码。

/** 
 * 自定义异常
 */
public class SystemException extends RuntimeException{
    private String code;//状态码
    public SystemException(String message, String code) {
        super(message);
        this.code = code;
    }
    public String getCode() {
        return code;
    }
}

第一种思路,设计一个基类

/**
 * 处理异常的类,需要处理异常的Controller直接继承这个类
 */
public class BaseController {
    /**
     * 处理Controller抛出的异常
     * @param e 异常实例
     * @return Controller层的返回值
     */
    @ExceptionHandler
    @ResponseBody
    public Object expHandler(Exception e){
        if(e instanceof SystemException){
            SystemException ex= (SystemException) e;
            return WebResult.buildResult().status(ex.getCode())
                            .msg(ex.getMessage());
        }else{
            e.printStackTrace();
            return WebResult.buildResult().status(Config.FAIL)
                            .msg("系统错误");
        }
    }
}

之后所有需要异常处理的Controller都继承这个类,从而获取到异常处理的方法。
虽然这种方式可以解决问题,但是极其不灵活,因为动用了继承机制就只为获取一个默认的方法,这显然是不好的。

第二种思路,将这个基类变为接口,提供此方法的默认实现(也就是接口中的default方法,java8开始支持接口方法的默认实现)

/**
 * 接口形式的异常处理
 */
public interface DataExceptionSolver {
    @ExceptionHandler
    @ResponseBody
    default Object exceptionHandler(Exception e){
        try {
            throw e;
        } catch (SystemException systemException) {
            systemException.printStackTrace();
            return WebResult.buildResult().status(systemException.getCode())
                    .msg(systemException.getMessage());
        } catch (Exception e1){
            e1.printStackTrace();
            return WebResult.buildResult().status(Config.FAIL)
                    .msg("系统错误");
        }
    }
}

这种方式虽然没有占用继承,但是也不是很优雅,因为几乎所有的Controller都需要进行异常处理,于是我每个Controller都需要去写implement DataExceptionSolver,这显然不是我真正想要的。况且这种方式依赖java8才有的语法,这是一个很大的局限。

第三种思路,使用加强Controller做全局异常处理。

来个案例

1、定义一个异常信息描述基础信息接口类

public interface BaseErrorInfoInterface {
    /** 错误码*/
     String getResultCode();

    /** 错误描述*/
     String getResultMsg();
}

2、定义一个枚举类实现上面的异常信息描述接口

public enum CommonEnum implements BaseErrorInfoInterface {
    // 数据操作错误定义
    SUCCESS("200", "成功!"), 
    BODY_NOT_MATCH("400","请求的数据格式不符!"),
    SIGNATURE_NOT_MATCH("401","请求的数字签名不匹配!"),
    NOT_FOUND("404", "未找到该资源!"), 
    INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),
    SERVER_BUSY("503","服务器正忙,请稍后再试!");

    /** 错误码 */
    private String resultCode;

    /** 错误描述 */
    private String resultMsg;

    CommonEnum(String resultCode, String resultMsg) {
        this.resultCode = resultCode;
        this.resultMsg = resultMsg;
    }

    @Override
    public String getResultCode() {
        return resultCode;
    }

    @Override
    public String getResultMsg() {
        return resultMsg;
    }

}

3、定义一个自定义异常类,标识业务系统出现的异常信息

public class BizException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    /**
     * 错误码
     */
    protected String errorCode;
    /**
     * 错误信息
     */
    protected String errorMsg;

    public BizException() {
        super();
    }

    public BizException(BaseErrorInfoInterface errorInfoInterface) {
        super(errorInfoInterface.getResultCode());
        this.errorCode = errorInfoInterface.getResultCode();
        this.errorMsg = errorInfoInterface.getResultMsg();
    }

    public BizException(BaseErrorInfoInterface errorInfoInterface, Throwable cause) {
        super(errorInfoInterface.getResultCode(), cause);
        this.errorCode = errorInfoInterface.getResultCode();
        this.errorMsg = errorInfoInterface.getResultMsg();
    }

    public BizException(String errorMsg) {
        super(errorMsg);
        this.errorMsg = errorMsg;
    }

    public BizException(String errorCode, String errorMsg) {
        super(errorCode);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public BizException(String errorCode, String errorMsg, Throwable cause) {
        super(errorCode, cause);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }


    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public String getMessage() {
        return errorMsg;
    }

    @Override
    public Throwable fillInStackTrace() {
        return this;
    }

}

4、定义一个统一结果返回数据封装类

public class ResultBody {
    /**
     * 响应代码
     */
    private String code;

    /**
     * 响应消息
     */
    private String message;

    /**
     * 响应结果
     */
    private Object result;

    public ResultBody() {
    }

    public ResultBody(BaseErrorInfoInterface errorInfo) {
        this.code = errorInfo.getResultCode();
        this.message = errorInfo.getResultMsg();
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }

    /**
     * 成功
     * 
     * @return
     */
    public static ResultBody success() {
        return success(null);
    }

    /**
     * 成功
     * @param data
     * @return
     */
    public static ResultBody success(Object data) {
        ResultBody rb = new ResultBody();
        rb.setCode(CommonEnum.SUCCESS.getResultCode());
        rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
        rb.setResult(data);
        return rb;
    }

    /**
     * 失败
     */
    public static ResultBody error(BaseErrorInfoInterface errorInfo) {
        ResultBody rb = new ResultBody();
        rb.setCode(errorInfo.getResultCode());
        rb.setMessage(errorInfo.getResultMsg());
        rb.setResult(null);
        return rb;
    }

    /**
     * 失败
     */
    public static ResultBody error(String code, String message) {
        ResultBody rb = new ResultBody();
        rb.setCode(code);
        rb.setMessage(message);
        rb.setResult(null);
        return rb;
    }

    /**
     * 失败
     */
    public static ResultBody error( String message) {
        ResultBody rb = new ResultBody();
        rb.setCode("-1");
        rb.setMessage(message);
        rb.setResult(null);
        return rb;
    }

    @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }

}

5、定义一个全局异常处理类

@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理自定义的业务异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = BizException.class)  
    @ResponseBody  
    public  ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){
        logger.error("发生业务异常!原因是:{}",e.getErrorMsg());
        return ResultBody.error(e.getErrorCode(),e.getErrorMsg());
    }

    /**
     * 处理空指针的异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =NullPointerException.class)
    @ResponseBody
    public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){
        logger.error("发生空指针异常!原因是:",e);
        return ResultBody.error(CommonEnum.BODY_NOT_MATCH);
    }


    /**
     * 处理其他异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =Exception.class)
    @ResponseBody
    public ResultBody exceptionHandler(HttpServletRequest req, Exception e){
        logger.error("未知异常!原因是:",e);
        return ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);
    }
}


说明:上面的代码,使用了@ControllerAdvice和@ExceptionHandler注解。其中@ControllerAdvice的作用是开启对全局异常的捕获,这个注解还可以通过assignableTypes参数指定特定的Controller类,让异常处理类只处理特定类抛出的异常。@ExceptionHandler注解,标明了该处理方法体处理的异常类型。

四、@InitBinder
SpringMVC并不是能对所有类型的参数进行绑定的,如果对日期Date类型参数进行绑定,就会报错IllegalStateException错误。所以需要注册一些类型绑定器用于对参数进行绑定。InitBinder注解就有这个作用。

    @InitBinder
    public void dateTypeBinder(WebDataBinder webDataBinder){
        //往数据绑定器中添加一个DateFormatter日期转化器。
        webDataBinder.addCustomFormatter(new DateFormatter("yyyy-mm-dd"));
    }

使用@InitBinder 注册的绑定器只有在当前Controller中才有效,不会作用于其他Controller。

如果觉得在每个Controller里面写太复杂,你可以写个BaseController,让其他Controller继承该类

我们可以自定义格式转化器,实现Formatter接口就可。还可以添加验证器等等。

public class StringFormatter implements Formatter {
    private static final String PREFIX = "prefix- ";

    @Override
    public String parse(String text, Locale locale) throws ParseException {
        //所以String类型参数都加上一个前缀。
        String result = PREFIX + text;
        return result;
    }

    @Override
    public String print(String object, Locale locale) {
        return object;
    }
}

然后添加到数据绑定器中

    @InitBinder
    public void dateTypeBinder(WebDataBinder webDataBinder){
        //往数据绑定器中添加一个DateFormatter日期转化器。
        webDataBinder.addCustomFormatter(new DateFormatter("yyyy-mm-dd"));
        // 添加一个String类型数据绑定器,作用是添加一个前缀
        webDataBinder.addCustomFormatter(new StringFormatter());
    }

当然,你也可以自己注册自定义的编辑器

自定义的编辑器类需要继承org.springframework.beans.propertyeditors.PropertiesEditor;并重写其setAsText和getAsText两个方法就行了

然后在InitBinder方法中注册就行。

这里提供给大家一个Demo

public class BaseController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Date.class, new MyDateEditor());
        binder.registerCustomEditor(Double.class, new DoubleEditor()); 
        binder.registerCustomEditor(Integer.class, new IntegerEditor());
    }

    private class MyDateEditor extends PropertyEditorSupport {
        @Override
        public void setAsText(String text) throws IllegalArgumentException {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = null;
            try {
                date = format.parse(text);
            } catch (ParseException e) {
                format = new SimpleDateFormat("yyyy-MM-dd");
                try {
                    date = format.parse(text);
                } catch (ParseException e1) {
                }
            }
            setValue(date);
        }
    }
    
    public class DoubleEditor extends PropertiesEditor  {    
        @Override    
        public void setAsText(String text) throws IllegalArgumentException {    
            if (text == null || text.equals("")) {    
                text = "0";    
            }    
            setValue(Double.parseDouble(text));    
        }    
        
        @Override    
        public String getAsText() {    
            return getValue().toString();    
        }    
    }  
    
    public class IntegerEditor extends PropertiesEditor {    
        @Override    
        public void setAsText(String text) throws IllegalArgumentException {    
            if (text == null || text.equals("")) {    
                text = "0";    
            }    
            setValue(Integer.parseInt(text));    
        }    
        
        @Override    
        public String getAsText() {    
            return getValue().toString();    
        }    
    }  

}


利用@InitBinder实现表单多对象传递小技巧

Student对象和Course对象:

public class Student implements Serializable{  
  String id;  
  String note;  
  //get..set....  
}  
public class Course implements Serializable{  
  String id;  
  String note;
  //set..get...  
}  

HTML页面:

 
     
     
     
     
     
 

Controller:

@Controller  
@RequestMapping("/classtest")  
public class TestController {  
    // 绑定变量名字和属性,参数封装进类  
    @InitBinder("student")  
    public void initBinderUser(WebDataBinder binder) {  
        // 这里的”.”千万别忘记了
        // 表示去掉前缀 student.
        binder.setFieldDefaultPrefix("student.");  
    }  
    // 绑定变量名字和属性,参数封装进类  
    @InitBinder("course")  
    public void initBinderAddr(WebDataBinder binder) {  
        binder.setFieldDefaultPrefix("course.");  
    }  


    @RequestMapping("/methodtest")  
    @ResponseBody  
   public Map test(Student student,@ModelAttribute("course") Course course){  
        Map map=new HashMap();  
        map.put("student", student);  
        map.put("course", course);  
        return map;  
    }

@InitBinder() 中间的value值,用于指定表单属性或请求参数的名字,符合该名字的将使用此处的DataBinder。比如:student.id和student.note。student就得是中间的value值,这样才能接收得到。而且student会填充进WebDataBinder,这里binder对象就是student了。也可以用@ModelAttribute("student")做限定。

你可能感兴趣的:(Java,springboot,Spring,java,spring,mybatis)