前言

接口类项目开发时,为了便于后期查找问题,一般会拦截器或过滤器中记录每个接口请求的参数与响应值记录,

请求参数很容易从request中获取,但controller的返回值无法从response中获取,有一个简单的方法,在controller接口的最后将返回值保存到request域中,这种方法虽然简单,但是开发起来太麻烦,需要在每个controller的最后添加一行代码,且该功能不属于业务功能,不应该接口中去实现,应该有个全局的处理方法。从而引出 ControllerAdvice 注解

ControllerAdvicespringmvc controller 增强器,其有三个作用:

ModelAttribute: 暴露@RequestMapping 方法返回值为模型数据:放在功能处理方法的返回值上时,是暴露功能处理方法的返回值为模型数据,用于视图页面展示时使用。
InitBinder : 用于自定义@RequestMapping 方法参数绑定
Exception : 用于@ResponseBody返回值增加处理

@ControllerAdvice 定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    @AliasFor("basePackages")
	String[] value() default {};
	@AliasFor("value")
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
	Class<?>[] assignableTypes() default {};
	Class<? extends Annotation>[] annotations() default {};
}

默认情况下,@ControllerAdvice 中的方法全局应用于所有控制器。使用选择器,如注释、basepackageclass和basePackages(或其别名值)来定义目标控制器的更小子集。如果声明了多个选择器,则应用布尔或逻辑,这意味着所选控制器应该匹配至少一个选择器。请注意,选择器检查是在运行时执行的,因此添加许多选择器可能会对性能产生负面影响并增加复杂性。

@ControllerAdvice:对于那些声明了@ExceptionHandler、@InitBinder或@ModelAttribute方法并要在多个@Controller类间共享的类,特殊化@Component。

总的来说,@ControllerAdvice 有其下作用

  • 全局异常处理
  • 全局数据绑定
  • 全局数据预处理

一、全局异常处理

方式一:结果为字符串

先定义接口:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public void hello(){
        System.out.println( 1 / 0);
    }
}

接口中存在一个算术异常,在定义一个全局异常处理类,用来处理异常

@RestControllerAdvice//类似 @RestController
public class GlobalException {
    @ExceptionHandler(ArithmeticException.class)
    public String globalException(ArithmeticException e){//返回字符串
        return e.toString();
    }
}

@ExceptionHandler 注解都可以在普通的 Controller 类上使用, @ControllerAdvice 只是作用范围可以自定义(默认全部)

当我们访问接口时,返回该异常的信息:

方式二:结果为页面

定义显示异常信息的页面:exception.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>exception-thymeleaf</h1>
<span th:text="${error}"></span>
</body>
</html>

在定义类用来拦截空指针异常:

@ControllerAdvice//类似 controller
public class GlobalException {
    @ExceptionHandler(Exception.class)
    public ModelAndView globalHtml(Exception e){
        ModelAndView mv = new ModelAndView("exception");
        mv.addObject("error",e);
        return mv;
    }
}

在该类中通过 modelandview 来显示页面与将异常信息返回到页面中

定义接口:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public void hello(){
        String[] strs = null;
        System.out.println(strs[0]);
    }
}

访问接口时:结果如下:

注意:对于@ExceptionHandler方法,在特定通知bean的处理程序方法中,根异常匹配将优于仅匹配当前异常的原因。但是,高优先级通知上的原因匹配仍然优于低优先级通知bean上的任何匹配(无论是根级还是原因级)。因此,请按照相应的顺序在优先级排序的通知bean上声明您的主根异常映射。

因为异常可能在任何时候引发,使模型的内容不可靠。由于这个原因,@ExceptionHandler方法不提供对Model参数的访问。

二、全局数据绑定

首先定义数据类:

@ControllerAdvice
public class GlobalData {
    //可通过 ModelAttribute 中的 name属性来设置传输数据名
    @ModelAttribute
    public Map<String,Object> myData(){
        Map<String,Object> map = new HashMap<>();
        map.put("name", "阿良");
        map.put("age", 20);
        return map;
    }
}

1、@ModelAttribute :将方法参数或方法返回值绑定到命名模型属性的注释,公开给web视图。支持带有 @RequestMapping 方法的控制器类。

2、@ModelAttribute 还可以通过在控制器类中使用 @RequestMapping 方法注释 accessor 方法来将引用数据暴露给web视图。允许这样的访问器方法具有@RequestMapping 方法支持的任何参数,返回要公开的模型属性值。

在接口中通过 model 来接收数据:

@RestController
public class HelloController {
    @GetMapping("/data")
    public void mydata(Model model){
        Map<String, Object> asMap = model.asMap();
        Map<String, Object> map = (Map<String, Object>) asMap.get("map");
        Set<String> keys = map.keySet();
        for (String key : keys) {
            System.out.println(key + "---->" + map.get(key));
        }
    }
}

其中 因为 @ModelAttribute 没有指定数据的 名称,所以默认为方法返回类型的首字母小写。

控制台成功输出数据。

三、全局数据预处理

首先定义两个model:

//book类
public class Book {
    private String name;
    private Integer price;
    //get、set、tostring
}
//author类
public class Author {
    private String name;
    private Integer age;
    //get、set、tostring
}

定义全局数据绑定类:

@ControllerAdvice
public class GlobalData {
    @InitBinder("b")
    public void b(WebDataBinder binder){
        binder.setFieldDefaultPrefix("b.");
    }
    @InitBinder("a")
    public void a(WebDataBinder binder){
        binder.setFieldDefaultPrefix("a.");
    }
}

其中 WebDataBinder 作用为:用于从web请求参数到JavaBean对象的数据绑定的特殊DataBinder。专为web环境,但不依赖于Servlet API;作为更具体的DataBinder变体(如ServletRequestDataBinder)的基类。

通过 @InitBinder :标识初始化org.springframework.web.bind.WebDataBinder方法的注释,该方法将用于填充带注释的处理程序方法的命令和表单对象参数。即在请求时,发送数据

在定义接口:

@RestController
public class GlobalDataProcessController {
    @PostMapping("/datapro")
    public void dataProcess(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author){
        System.out.println(book);
        System.out.println(author);
    }
}

当我们通过接口地址时,可携带数据发送。这里通过 postman 来测试:

这时,控制台输出为:

注意:默认情况下,@ControllerAdvice 中的方法全局应用于所有控制器。使用选择器,如注释、basepackageclass和basePackages(或其别名值)来定义目标控制器的更小子集。如果声明了多个选择器,则应用布尔或逻辑,这意味着所选控制器应该匹配至少一个选择器。请注意,选择器检查是在运行时执行的,因此添加许多选择器可能会对性能产生负面影响并增加复杂性。

posted on 2023-06-15 16:08  xashould  阅读(69)  评论(0编辑  收藏  举报