0


注解@RestControllerAdvice用法途

文章目录

一、@RestControllerAdvice是什么

@RestControllerAdvice

是一个组合注解,由

@ControllerAdvice

@ResponseBody

组成,而

@ControllerAdvice

继承了@Component,因此

@RestControllerAdvice

本质上是个

Component

,用于定义

@ExceptionHandler

@InitBinder

@ModelAttribute

方法,适用于所有使用

@RequestMapping

方法。

二、@RestControllerAdvice的特点

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

@ControllerAdvice可以指定 Controller 范围

  • basePackages: 指定一个或多个包,这些包及其子包下的所有 Controller 都被该 @ControllerAdvice 管理
@RestControllerAdvice(basePackages={"top.onething"})@Slf4jpublicclassExceptionHandlerAdvice{@ExceptionHandler(Exception.class)publicStringhandleException(Exception e){return"error";}}
  • basePackageClasses: 是 basePackages 的一种变形,指定一个或多个 Controller 类,这些类所属的包及其子包下的所有 Controller 都被该 @ControllerAdvice 管理
@RestControllerAdvice(basePackageClasses={TestController.class})@Slf4jpublicclassExceptionHandlerAdvice{@ExceptionHandler(Exception.class)publicStringhandleException(Exception e){return"error";}}
  • assignableTypes: 指定一个或多个 Controller 类,这些类被该 @ControllerAdvice 管理
@RestControllerAdvice(assignableTypes={TestController.class})@Slf4jpublicclassExceptionHandlerAdvice{@ExceptionHandler(Exception.class)publicStringhandleException(Exception e){return"error";}}
  • annotations: 指定一个或多个注解,被这些注解所标记的 Controller 会被该 @ControllerAdvice 管理
@ControllerAdvice(annotations ={TestAnnotation.class})@Slf4jpublicclassExceptionHandlerAdvice{@ExceptionHandler(Exception.class)publicStringhandleException(Exception e){return"error";}}

三、@ExceptionHandler

我们可以搭配

@ResponseStatus

:可以将某种异常映射为HTTP状态码

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

/** 
 * 自定义异常
 */publicclassSystemExceptionextendsRuntimeException{privateString code;//状态码publicSystemException(String message,String code){super(message);this.code = code;}publicStringgetCode(){return code;}}

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

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

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

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

/**
 * 接口形式的异常处理
 */publicinterfaceDataExceptionSolver{@ExceptionHandler@ResponseBodydefaultObjectexceptionHandler(Exception e){try{throw e;}catch(SystemException systemException){
            systemException.printStackTrace();returnWebResult.buildResult().status(systemException.getCode()).msg(systemException.getMessage());}catch(Exception e1){
            e1.printStackTrace();returnWebResult.buildResult().status(Config.FAIL).msg("系统错误");}}}

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

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

来个案例

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

publicinterfaceBaseErrorInfoInterface{/** 错误码*/StringgetResultCode();/** 错误描述*/StringgetResultMsg();}

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

publicenumCommonEnumimplementsBaseErrorInfoInterface{// 数据操作错误定义SUCCESS("200","成功!"),BODY_NOT_MATCH("400","请求的数据格式不符!"),SIGNATURE_NOT_MATCH("401","请求的数字签名不匹配!"),NOT_FOUND("404","未找到该资源!"),INTERNAL_SERVER_ERROR("500","服务器内部错误!"),SERVER_BUSY("503","服务器正忙,请稍后再试!");/** 错误码 */privateString resultCode;/** 错误描述 */privateString resultMsg;CommonEnum(String resultCode,String resultMsg){this.resultCode = resultCode;this.resultMsg = resultMsg;}@OverridepublicStringgetResultCode(){return resultCode;}@OverridepublicStringgetResultMsg(){return resultMsg;}}

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

publicclassBizExceptionextendsRuntimeException{privatestaticfinallong serialVersionUID =1L;/**
     * 错误码
     */protectedString errorCode;/**
     * 错误信息
     */protectedString errorMsg;publicBizException(){super();}publicBizException(BaseErrorInfoInterface errorInfoInterface){super(errorInfoInterface.getResultCode());this.errorCode = errorInfoInterface.getResultCode();this.errorMsg = errorInfoInterface.getResultMsg();}publicBizException(BaseErrorInfoInterface errorInfoInterface,Throwable cause){super(errorInfoInterface.getResultCode(), cause);this.errorCode = errorInfoInterface.getResultCode();this.errorMsg = errorInfoInterface.getResultMsg();}publicBizException(String errorMsg){super(errorMsg);this.errorMsg = errorMsg;}publicBizException(String errorCode,String errorMsg){super(errorCode);this.errorCode = errorCode;this.errorMsg = errorMsg;}publicBizException(String errorCode,String errorMsg,Throwable cause){super(errorCode, cause);this.errorCode = errorCode;this.errorMsg = errorMsg;}publicStringgetErrorCode(){return errorCode;}publicvoidsetErrorCode(String errorCode){this.errorCode = errorCode;}publicStringgetErrorMsg(){return errorMsg;}publicvoidsetErrorMsg(String errorMsg){this.errorMsg = errorMsg;}publicStringgetMessage(){return errorMsg;}@OverridepublicThrowablefillInStackTrace(){returnthis;}}

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

publicclassResultBody{/**
     * 响应代码
     */privateString code;/**
     * 响应消息
     */privateString message;/**
     * 响应结果
     */privateObject result;publicResultBody(){}publicResultBody(BaseErrorInfoInterface errorInfo){this.code = errorInfo.getResultCode();this.message = errorInfo.getResultMsg();}publicStringgetCode(){return code;}publicvoidsetCode(String code){this.code = code;}publicStringgetMessage(){return message;}publicvoidsetMessage(String message){this.message = message;}publicObjectgetResult(){return result;}publicvoidsetResult(Object result){this.result = result;}/**
     * 成功
     * 
     * @return
     */publicstaticResultBodysuccess(){returnsuccess(null);}/**
     * 成功
     * @param data
     * @return
     */publicstaticResultBodysuccess(Object data){ResultBody rb =newResultBody();
        rb.setCode(CommonEnum.SUCCESS.getResultCode());
        rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
        rb.setResult(data);return rb;}/**
     * 失败
     */publicstaticResultBodyerror(BaseErrorInfoInterface errorInfo){ResultBody rb =newResultBody();
        rb.setCode(errorInfo.getResultCode());
        rb.setMessage(errorInfo.getResultMsg());
        rb.setResult(null);return rb;}/**
     * 失败
     */publicstaticResultBodyerror(String code,String message){ResultBody rb =newResultBody();
        rb.setCode(code);
        rb.setMessage(message);
        rb.setResult(null);return rb;}/**
     * 失败
     */publicstaticResultBodyerror(String message){ResultBody rb =newResultBody();
        rb.setCode("-1");
        rb.setMessage(message);
        rb.setResult(null);return rb;}@OverridepublicStringtoString(){returnJSONObject.toJSONString(this);}}

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

@ControllerAdvicepublicclassGlobalExceptionHandler{privatestaticfinalLogger logger =LoggerFactory.getLogger(GlobalExceptionHandler.class);/**
     * 处理自定义的业务异常
     * @param req
     * @param e
     * @return
     */@ExceptionHandler(value =BizException.class)@ResponseBodypublicResultBodybizExceptionHandler(HttpServletRequest req,BizException e){
        logger.error("发生业务异常!原因是:{}",e.getErrorMsg());returnResultBody.error(e.getErrorCode(),e.getErrorMsg());}/**
     * 处理空指针的异常
     * @param req
     * @param e
     * @return
     */@ExceptionHandler(value =NullPointerException.class)@ResponseBodypublicResultBodyexceptionHandler(HttpServletRequest req,NullPointerException e){
        logger.error("发生空指针异常!原因是:",e);returnResultBody.error(CommonEnum.BODY_NOT_MATCH);}/**
     * 处理其他异常
     * @param req
     * @param e
     * @return
     */@ExceptionHandler(value =Exception.class)@ResponseBodypublicResultBodyexceptionHandler(HttpServletRequest req,Exception e){
        logger.error("未知异常!原因是:",e);returnResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);}}

说明:上面的代码,使用了

@ControllerAdvice

@ExceptionHandler

注解。其中

@ControllerAdvice

的作用是开启对全局异常的捕获,这个注解还可以通过

assignableTypes

参数指定特定的

Controller

类,让异常处理类只处理特定类抛出的异常。

@ExceptionHandler

注解,标明了该处理方法体处理的异常类型。

四、@InitBinder

SpringMVC并不是能对所有类型的参数进行绑定的,如果对日期Date类型参数进行绑定,就会报错

IllegalStateException

错误。所以需要注册一些类型绑定器用于对参数进行绑定。InitBinder注解就有这个作用。

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

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

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

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

publicclassStringFormatterimplementsFormatter<String>{privatestaticfinalString PREFIX ="prefix- ";@OverridepublicStringparse(String text,Locale locale)throwsParseException{//所以String类型参数都加上一个前缀。String result = PREFIX + text;return result;}@OverridepublicStringprint(String object,Locale locale){return object;}}

然后添加到数据绑定器中

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

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

自定义的编辑器类需要继承

org.springframework.beans.propertyeditors.PropertiesEditor;

并重写其setAsText和getAsText两个方法就行了

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

这里提供给大家一个Demo

publicclassBaseController{@InitBinderprotectedvoidinitBinder(WebDataBinder binder){
        binder.registerCustomEditor(Date.class,newMyDateEditor());
        binder.registerCustomEditor(Double.class,newDoubleEditor()); 
        binder.registerCustomEditor(Integer.class,newIntegerEditor());}privateclassMyDateEditorextendsPropertyEditorSupport{@OverridepublicvoidsetAsText(String text)throwsIllegalArgumentException{SimpleDateFormat format =newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date date =null;try{
                date = format.parse(text);}catch(ParseException e){
                format =newSimpleDateFormat("yyyy-MM-dd");try{
                    date = format.parse(text);}catch(ParseException e1){}}setValue(date);}}publicclassDoubleEditorextendsPropertiesEditor{@OverridepublicvoidsetAsText(String text)throwsIllegalArgumentException{if(text ==null|| text.equals("")){    
                text ="0";}setValue(Double.parseDouble(text));}@OverridepublicStringgetAsText(){returngetValue().toString();}}publicclassIntegerEditorextendsPropertiesEditor{@OverridepublicvoidsetAsText(String text)throwsIllegalArgumentException{if(text ==null|| text.equals("")){    
                text ="0";}setValue(Integer.parseInt(text));}@OverridepublicStringgetAsText(){returngetValue().toString();}}}

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

Student对象和Course对象:

publicclassStudentimplementsSerializable{String id;String note;//get..set....  }publicclassCourseimplementsSerializable{String id;String note;//set..get...  }

HTML页面:

<formaction="/test/test"method="get"><inputtype="text"name="student.id"value="student_id"><inputtype="text"name="student.name"value="student_name"><inputtype="text"name="course.id"value="course_id"><inputtype="text"name="course.name"value="course_name"><inputtype="submit"value="提交"></form>

Controller:

@Controller@RequestMapping("/classtest")publicclassTestController{// 绑定变量名字和属性,参数封装进类  @InitBinder("student")publicvoidinitBinderUser(WebDataBinder binder){// 这里的”.”千万别忘记了// 表示去掉前缀 student.
        binder.setFieldDefaultPrefix("student.");}// 绑定变量名字和属性,参数封装进类  @InitBinder("course")publicvoidinitBinderAddr(WebDataBinder binder){  
        binder.setFieldDefaultPrefix("course.");}@RequestMapping("/methodtest")@ResponseBodypublicMap<String,Object>test(Student student,@ModelAttribute("course")Course course){Map<String,Object> map=newHashMap<String,Object>();  
        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 spring jvm

本文转载自: https://blog.csdn.net/m0_51620667/article/details/127013347
版权归原作者 oah1021 所有, 如有侵权,请联系我们删除。

“注解@RestControllerAdvice用法途”的评论:

还没有评论