@ControllerAdvice是在SpringMVC中的,Spring Boot可以直接使用。下面在Spring Boot中使用@ControllerAdvice的不同功能。
@ControllerAdvice注解主要有以下三个功能:
- 处理全局异常
- 预设全局数据
- 全局数据预处理
一、处理全局异常
Spring Boot提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异常内容。
例如:
设置我们控制器中发生错误,如
@GetMapping("/test")
public String test() {
int i=5/0;
return "success";
}
这时访问会出现下面的报错页,该页面就是Spring Boot提供的默认error映射页面。
这样的页面对用户不友好,我们一般对全局异常做统一处理。
我们可以通过使用@ControllerAdvice来定义统一的异常处理类,而不是在每个Controller中逐个定义。而@ExceptionHandler用来定义函数针对的异常类型,最后将Exception对象和请求URL映射到error.html中。
如:创建一个MyGlobalExceptionHandler类
@ControllerAdvice
public class MyGlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView myError(Exception e, HttpServletRequest request){
ModelAndView mav=new ModelAndView();
mav.setViewName("myerror");
mav.addObject("url",request.getRequestURL());
mav.addObject("error",e.getMessage());
return mav;
}
}
项目中的Maven依赖有
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
myerror.html实现,需要先在项目中引入Thymeleaf模板的依赖:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Error Page</h1>
<h2 th:text="${url}"></h2>
<div>发生错误:</div>
<h2 th:text="${error}"></h2>
</body>
</html>
这样再次访问test,可以看到如下页面:
在@ControllerAdvice注解的类中,可以定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法、专门处理数组越界的方法等,其中使用@ExceptionHandler匹配抛出的异常类型。
也可以直接像上面代码一样,在一个方法中处理所有的异常信息。
二、预设全局数据
我们可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中都能够访问调用这些数据。
使用示例:首先在添加了 @ControllerAdvice 注解的类中定义全局数据
@ControllerAdvice
public class MyGlobalExceptionHandler {
@ModelAttribute("md")
public Map<String,Object> myData(){
Map<String,Object> map=new HashMap<String, Object>();
map.put("name","张三");
map.put("age",18);
return map;
}
}
这里返回的全局数据map的key默认就是变量名即map,这里可以通过@ModelAttribute注解的name属性进行自定义key。
定义一个控制器。提供接口“/getData”进行访问
@RestController
public class TestController {
@GetMapping("/getData")
public void getData(Model model){
Map<String,Object> map=model.asMap();
Set<String> set=map.keySet();
for(String s:set){
System.out.println(s+":"+map.get(s));
}
}
}
在控制台中查看是否取到值
三、全局数据预处理
@ControllerAdvice还能用于预处理请求参数,当前端传递两个实体类的参数,而两个实体类有相同属性时。就需要对参数进行预处理了。
例如:有一个Teacher类,有id和name属性。
public class Teacher {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
另外有个Student类,这两个类有相同的属性,如下:
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
定义一个Controller
@RestController
public class TestController {
@PostMapping("/setData")
public void setData(Teacher teacher,Student student){
System.out.println(teacher);
System.out.println(student);
}
}
通过Postman发送post请求数据
查看控制台输入的数据:
可以看到收到的数据有问题,前端发来的数据,因为接受数据的两个实体类属性相同,所以无法区分。
解决方案:
在 @ControllerAdvice 注解的类中添加如下代码:
@InitBinder("s")
public void s(WebDataBinder binder){
binder.setFieldDefaultPrefix("s.");
}
@InitBinder("t")
public void t(WebDataBinder binder){
binder.setFieldDefaultPrefix("t.");
}
@InitBinder搭配配置WebDataBinder,自动绑定前台请求参数到Model中。
上面的使用:
@InitBinder(“s”) 注解表示该方法用来处理和Student实体类相关的参数,在该注解下的方法中,给参数添加一个 s前缀,那么我们请求参数要有s前缀。
@InitBinder(“t”) 注解表示该方法用来处理和Teacher实体类相关的参数,在该注解下的方法中,给参数添加一个 t前缀,那么我们请求参数要有t前缀。
接口方法中使用@ModelAttribute注解给变量其别名
@PostMapping("/setData")
public void setData(@ModelAttribute("t") Teacher teacher, @ModelAttribute("s")Student student){
System.out.println(teacher);
System.out.println(student);
}
请求发送时,通过给不同对象的参数添加不同的前缀,可以实现参数的区分。
查看控制台输出,可以看到数据获取正确。