注解@RestControllerAdvice用法途
文章目录
一、@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"})@Slf4jpublic class ExceptionHandlerAdvice { @ExceptionHandler(Exception.class) public String handleException(Exception e) { return "error"; } }
- basePackageClasses: 是 basePackages 的一种变形,指定一个或多个 Controller 类,这些类所属的包及其子包下的所有 Controller 都被该 @ControllerAdvice 管理
@RestControllerAdvice(basePackageClasses={TestController.class})@Slf4jpublic class ExceptionHandlerAdvice { @ExceptionHandler(Exception.class) public String handleException(Exception e) { return "error"; } }
- assignableTypes: 指定一个或多个 Controller 类,这些类被该 @ControllerAdvice 管理
@RestControllerAdvice(assignableTypes={TestController.class})@Slf4jpublic class ExceptionHandlerAdvice { @ExceptionHandler(Exception.class) public String handleException(Exception e) { return "error"; } }
- annotations: 指定一个或多个注解,被这些注解所标记的 Controller 会被该 @ControllerAdvice 管理
@ControllerAdvice(annotations = {TestAnnotation.class})@Slf4jpublic 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; }}
第一种思路,设计一个基类
public class BaseController { @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; } public static ResultBody success() { return success(null); } 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、定义一个全局异常处理类
@ControllerAdvicepublic class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(value = BizException.class) @ResponseBody public ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){ logger.error("发生业务异常!原因是:{}",e.getErrorMsg()); return ResultBody.error(e.getErrorCode(),e.getErrorMsg()); } @ExceptionHandler(value =NullPointerException.class) @ResponseBody public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){ logger.error("发生空指针异常!原因是:",e); return ResultBody.error(CommonEnum.BODY_NOT_MATCH); } @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<String> { 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页面:
<form action="/test/test" method="get"> <input type="text" name="student.id" value="student_id"> <input type="text" name="student.name" value="student_name"> <input type="text" name="course.id" value="course_id"> <input type="text" name="course.name" value="course_name"> <input type="submit" value="提交"> form>
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<String,Object> test(Student student,@ModelAttribute("course") Course course){ Map<String,Object> map=new HashMap<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")
做限定。
来源地址:https://blog.csdn.net/m0_51620667/article/details/127013347
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341