@RestControllerAdvice = @ControllerAdvice + @ResponseBody
@ControllerAdvice 就是 @Controller 的增强版。@ControllerAdvice 主要用来处理全局数据,一般搭配 @ExceptionHandler、@ModelAttribute、@InitBinder使用,在三个应用场景中使用:全局异常处理、全局数据绑定和全局数据预处理。@ControllerAdvice是@Component注解的一个延伸注解,Spring会自动扫描并能检测到被@ControllerAdvice所标注的类。
SpringBoot - @RequestBody、@ResponseBody的使用场景
适用于所有的@RequestMapping标注的方法上,也就是说该注解应该添加在被@RestController/@Controller注解标注的类中的方法上。
A. 创建一个全局异常类;
B. 在该全局异常类上添加注解:@ControllerAdvice;
C. 在该全局异常类中定义"方法1",并添加注解:@ExceptionHandler(AccessDeniedException.class);
其中定义的 AccessDeniedException.class 表明该"方法1"用来处理 AccessDeniedException类型的异常。
D. 在该全局异常类中定义"方法2",并添加注解:@ExceptionHandler(Exception.class);
表明该"方法2"用来处理所有类型的异常。
E. 在该全局异常类中定义的方法的参数,可以是异常类的实例、HttpServletResponse、HttpServletRequest或者Model 等;
F. 在该全局异常类中定义的方法的返回值可以是一段 JSON、一个 ModelAndView、一个逻辑视图名等等;
G. @ControllerAdvice可以指定扫描路径:@ControllerAdvice(basePackages={“cn.hadoopx.system”, “cn.hadoopx.common”})
优点:将Controller层的异常和数据校验的异常进行统一处理,减少模板try-catch的代码,减少编码量,提升扩展性和可维护性。
缺点:只能处理Controller层未捕获的异常,无法捕获拦截器层和SPRING框架层的异常。
/**
* 全局异常处理器
*
* @author ROCKY
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 权限校验异常
*/
@ExceptionHandler(AccessDeniedException.class)
public Result handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
return Result.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
}
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return Result.error(e.getMessage());
}
/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public Result handleServiceException(ServiceException e, HttpServletRequest request) {
log.error(e.getMessage(), e);
Integer code = e.getCode();
return StringUtils.isNotNull(code) ?
Result.error(code, e.getMessage()) : Result.error(e.getMessage());
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public Result handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return Result.error(e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public Result handleException(Exception e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return Result.error(e.getMessage());
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public Result handleBindException(BindException e) {
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return Result.error(message);
}
/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.error(message);
}
/**
* 演示模式异常
*/
@ExceptionHandler(DemoModeException.class)
public Result handleDemoModeException(DemoModeException e) {
return Result.error("演示模式,不允许操作");
}
}
全局数据绑定可以用来做一些初始化数据的操作,可以将一些公共的数据定义在添加了@ControllerAdvice注解的类中,这样的话每个Controller接口都能访问这些数据。
A. 使用@Model Attribute 注解标记的方法,返回数据是一个全局数据;
B. @ModelAttribute(value = “looker”), 其中 value 属性表示这条返回数据的KEY,而方法的返回值是返回数据的VALUE。
@ControllerAdvice
public class TheLooker{
// 方式1
@ModelAttribute(value = "looker")
public Map<String, String> globalData() {
HashMap<String, String> map = new HashMap<>();
map.put("name", "ROCKY");
map.put("age", "30");
map.put("info", "rich");
return map;
}
// 方式2, @ModelAttribute也可以不写value参数,直接在方法中对全局Model设置key和value
@ModelAttribute
public void addAttributes(Model model) {
HashMap<String, String> map = new HashMap<>();
map.put("name", "ROCKY");
map.put("age", "30");
map.put("info", "rich");
model.addAttribute("looker", map);
}
}
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(Model model) {
Map<String, Object> map = model.asMap();
// 获取全局数据
Map<String, String> info = (Map<String, String>)map.get("looker");
String result = "info:" + info;
return result;
}
}
SpringBoot - @InitBinder注解详解
A. 在Controller中定义一个方法public void initBinder(WebDataBinder binder) {};
B. 在initBinder方法上添加注解:@InitBinder;
C. 一般会定义一个Controller的基类,在该基类中定义initBinder方法。
/**
* WEB层通用数据处理
* @author ROCKY
*/
public class BaseController {
/**
* 将前台传递过来的日期格式的字符串转化为Date类型,否则无法将数据绑定到实体中。
* 自定义类型转换器有两种方式:A. implements Converter 或者 B. extends PropertyEditorSupport;
* 在WebDataBinder对象中,可以设置前缀,可以设置允许、禁止的字段、必填字段以及验证器,可以自定义类型转换器。
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
setValue(DateUtils.parseDate(text));
}
});
}
}