@ControllerAdvice是SpringMVC里的一个注解,在SpringBoot中可以直接使用
@RestControllerAdvice 相当于@ControllerAdvice + @ResponseBody,返回的是json
ControllerAdvice有三个使用场景:
1.全局异常处理
@Slf4j
@RestControllerAdvice(basePackages = "com.lilei.gulimall.product.controller")
public class GulimallExceptionController {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleVaildException(MethodArgumentNotValidException e){
log.error("异常类型{}, 数据校验出现问题{}",e.getClass(),e.getMessage());
BindingResult result = e.getBindingResult();
if (result.hasErrors()) {
HashMap<String, String> map = new HashMap<>();
result.getFieldErrors().forEach((item)->{
String defaultMessage = item.getDefaultMessage();//获取错误提示
String field = item.getField();//获取错误的属性的名字
map.put(field,defaultMessage);
});
return R.error(400,"提交的数据不合法").put("data",map);
}
return R.ok();
}
}
1.1 @RestControllerAdvice
@RestControllerAdvice(basePackages = “要拦截的异常的包路径”)
如果你不想把包名写死,不如把包里的某个类传进去,这样包名重构了也不怕:
@RestControllerAdvice(basePackageClasses = BrandController.class)
如果你只想对某几个控制器添加通知,可以这样写:
@RestControllerAdvice(assignableTypes = {BrandController.class,AttrController.class})
1.2@ExceptionHandler(value = MethodArgumentNotValidException.class)
value是你要拦截的异常
2. 定义全局参数
2.1@ModelAttribute 请求过程中对参数进行设置,其中 value 属性表示这条返回数据的 key,而方法的返回值是返回数据的 value。
@ControllerAdvice(annotations = {Controller.class, RestController.class})
public class WebControllerAdvice {
@ModelAttribute(value = "param")
public String m(){
return "10001";
}
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("msg", "欢迎访问 hangge.com");
HashMap<String, String> map = new HashMap<>();
map.put("name", "hangge");
map.put("age", "100");
model.addAttribute("info", map);
}
}
//使用ModelAttribute定义的全局参数
@RestController
public class HelthCheckController {
@RequestMapping(value = "version/status", method = RequestMethod.GET)
public String health(@ModelAttribute("param") String param){
return param;
}
//也可以在请求的 Controller 方法参数中的 Model 获取所有的全局数据,下面代码的结果同上面的一样:
@GetMapping("/hello")
public String hello(Model model) {
Map<String, Object> map = model.asMap();
String msg = map.get("msg").toString();
Map<String, String> info = (Map<String, String>)map.get("info");
String result = "msg:" + msg + "
" + "info:" + info;
return result;
}
}
2.2 当然 @ModelAttribute 也可以不写 value 参数,直接在方法中对全局 Model 设置 key 和 value。下面代码的效果同上面是一样的:
3. 全局请求数据预处理
@InitBinder,当多个不同实体的多个参数名称相同时,使用前缀进行区分
考虑我有两个实体类,Book 和 Author,分别定义如下:
public class Book {
private String name;
private Long price;
//getter/setter
}
public class Author {
private String name;
private Integer age;
//getter/setter
}
此时,如果我定义一个数据添加接口,如下:
@PostMapping("/book")
public void addBook(Book book, Author author) {
System.out.println(book);
System.out.println(author);
}
这个时候,添加操作就会有问题,因为两个实体类都有一个 name 属性,从前端传递时 ,无法区分。此时,通过 @ControllerAdvice 的全局数据预处理可以解决这个问题
解决步骤如下:
1.给接口中的变量取别名
@PostMapping("/book")
public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) {
System.out.println(book);
System.out.println(author);
}
2.进行请求数据预处理
在 @ControllerAdvice 标记的类中添加如下代码:
@InitBinder("b")
public void b(WebDataBinder binder) {
binder.setFieldDefaultPrefix("b.");
}
@InitBinder("a")
public void a(WebDataBinder binder) {
binder.setFieldDefaultPrefix("a.");
}
@InitBinder(“b”) 注解表示该方法用来处理和Book和相关的参数,在方法中,给参数添加一个 b 前缀,即请求参数要有b前缀.
补充说明:在 WebDataBinder 对象中,除了可以设置前缀,还可以设置允许、禁止的字段、必填字段以及验证器等等。
3.发送请求
请求发送时,通过给不同对象的参数添加不同的前缀,可以实现参数的区分.
4.使用@InitBinder格式化Date类型参数问题
在实际开发中后端某个参数是Date类型,而前端传回来的值是String类型,这时后端接参时不会自动转化为Date类型。需要手动配置,自定义数据的绑定,这个时候就用到了@InitBinder。
我们只需要在Controller中配置如下代码就会实现Date类型自动转换。
@InitBinder
public void initBinder(ServletRequestDataBinder binder) {
/**
* 自动转换日期类型
*/
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
binder.registerCustomEditor(Date.class, new CustomDateEditor(format, true));
}
配置完之后,如果前端传来的数据是正常的日期数据是没有问题的,但是如果某个Date类型的字段不是必传的这时也许就会出现前端传回的字段值为null,此时对日期的格式化就会报错。
解决这个问题,其实前后端都可以解决。
这时对后端来说有一个很简单的解决方式,那就是让前端判断当这个日期的字段是空时,就不传这个字段,如果不传这个字段后端就会把这个日期字段置为null,从而不影响程序。
但是如果前端同事不服他们不改的话,那就后端改呗。
后端改其实也挺简单,那就是自定义一个编辑器类,可以写一个内部类继承原有的PropertyEditorSupport这种自定义的其实也很简单,而且还可以支持多种日期格式的转换。
public class TestController {
@InitBinder
public void initBinder(ServletRequestDataBinder binder) {
/*** 自动转换日期类型的字段格式
*/
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
binder.registerCustomEditor(Date.class,new DateEditor());
}
private class DateEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
//转换yyyy-MM-dd HH:mm:ss格式数据
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;
//如果不为空就格式化
if(!StringUtils.isEmpty(text)){
try {
date = format.parse(text);
} catch (ParseException e) {
//转换为yyyy-MM-dd格式数据
format = new SimpleDateFormat("yyyy-MM-dd");
try {
date = format.parse(text);
} catch (ParseException e1) {
}
}
}
setValue(date);
}
}
}