spring框架:注解@ControllerAdvice的三个使用场景

@ControllerAdvice是SpringMVC里的一个注解,在SpringBoot中可以直接使用
@RestControllerAdvice 相当于@ControllerAdvice + @ResponseBody,返回的是json

ControllerAdvice有三个使用场景:

  1. 全局异常处理
  2. 全局数据绑定
  3. 全局请求数据预处理

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.发送请求

请求发送时,通过给不同对象的参数添加不同的前缀,可以实现参数的区分.
spring框架:注解@ControllerAdvice的三个使用场景_第1张图片
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);
        }
    }
}

你可能感兴趣的:(Spring)