文章目录
一、@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
都能获得在此处设置的键值对
@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")
做限定。
版权归原作者 oah1021 所有, 如有侵权,请联系我们删除。