本章内容:
- @ControllerAdvice的三种使用方法以及具体场景
- 在SpringBoot中如何配置全局异常处理
- 如何添加异常处理方法
- 如何自定义异常
一、@ControllerAdvice的三种用法
@ControllerAdvice ,这是一个增强的 Controller。使用这个 Controller的注解 ,可以实现三个方面的功能:
- 全局异常处理
- 全局数据绑定
- 全局数据预处理
灵活使用这三个功能,可以帮助我们简化很多工作,需要注意的是,这是 SpringMVC 提供的功能,在 Spring Boot 中可以直接使用,下面分别来看。
1.1 全局异常处理
使用 @ControllerAdvice 实现全局异常处理,只需要定义类,添加该注解即可定义方式如下:
@ControllerAdvice
public class MyGlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView customException(Exception e) {
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("myerror");
return mv;
}
}
在该类中,可以定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法、专门处理数组越界的方法…,也可以直接向上面代码一样,在一个方法中处理所有的异常信息。
@ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为 NullpointerException,则数组越界异常就不会进到这个方法中来。
1.2 全局数据绑定
全局数据绑定功能可以用来做一些初始化的数据操作,我们可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中,就都能够访问导致这些数据。
使用步骤,首先定义全局数据,如下:
@ControllerAdvice
public class MyGlobalExceptionHandler {
@ModelAttribute(name = "md")
public Map<String,Object> mydata() {
HashMap<String, Object> map = new HashMap<>();
map.put("age", 99);
map.put("gender", "男");
return map;
}
}
使用 @ModelAttribute 注解标记该方法的返回数据是一个全局数据,默认情况下,这个全局数据的 key 就是返回的变量名,value 就是方法返回值,当然开发者可以通过 @ModelAttribute 注解的 name 属性去重新指定 key。
定义完成后,在任何一个Controller 的接口中,都可以获取到这里定义的数据:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(Model model) {
Map<String, Object> map = model.asMap();
System.out.println(map);
int i = 1 / 0;
return "hello controller advice";
}
}
测试效果
1.3 全局数据预处理
定义有两个实体类,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前缀
3.发送请求
请求发送时,通过给不同对象的参数添加不同的前缀,可以实现参数的区分.
二、SpringBoot当中的全局异常处理
在项目开发过程中,不管是对底层数据库的操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。如果对每个过程都单独作异常处理,那系统的代码耦合度会变得很高,此外,开发工作量也会加大而且不好统一,这也增加了代码的维护成本。 针对这种实际情况,我们需要将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能单一,也实现了异常信息的统一处理和维护。同时,我们也不希望直接把异常抛给用户,应该对异常进行处理,对错误信息进行封装,然后返回一个友好的信息给用户。这节主要总结一下项目中如何使用 Spring Boot 如何拦截并处理全局的异常。
关键注解:
@ControllerAdvice
捕获Controller
层抛出的异常,如果添加@ResponseBody
返回信息则为JSON
格式@RestControllerAdvice
相当于@ControllerAdvice
与@ResponseBody
的结合体@ExceptionHandler
统一处理一种类的异常,减少代码重复率,降低复杂度
参考技术文档:https://www.ycbbs.vip/?p=1281
2.1 创建全局统一异常处理器
第一步:创建统一异常处理器
package com.test.common.handler;
/**
* 统一异常处理类
*/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return Result.error();
}
}
第二步:扫描异常处理器
在SpringBoot启动类上添加了注解@ComponentScan
@ComponentScan(basePackages={"com.test.common"})
第三步:测试
2.2 添加异常处理方法
在GlobalExceptionHandler.java中添加异常(可以根据实际项目中出现的异常后进行异常处理方法添加,也可以提前将所有常用的异常提前罗列出来)
@ExceptionHandler(BadSqlGrammarException.class)
@ResponseBody
public Result error(BadSqlGrammarException e){
e.printStackTrace();
return Result.setResult(ResultCodeEnum.BAD_SQL_GRAMMAR);
}
在添加了统一日志处理后,可以将异常处理方法改为
@ExceptionHandler(BadSqlGrammarException.class)
@ResponseBody
public Result error(BadSqlGrammarException e){
log.error(ExceptionUtil.getMessage(e));
return Result.setResult(ResultCodeEnum.BAD_SQL_GRAMMAR);
}
这样修改后日志就会输出到相应的日志系统当中了
2.3 自定义异常
1)自定义异常
common模块中创建com.test.common.exception包,创建TestException.java通用异常类 继承 RuntimeException,其中,RuntimeException 对代码没有侵入性
package com.test.common.exception;
@Data
@ApiModel(value = "全局异常")
public class TestException extends RuntimeException {
@ApiModelProperty(value = "状态码")
private Integer code;
/**
* 接受状态码和消息
* @param code
* @param message
*/
public TestException(Integer code, String message) {
super(message);
this.code=code;
}
}
2)在特定位置跑出自定义异常
public Result pageQuery(......){
if(page <= 0 || limit <= 0){
//throw new TestException(21003, "参数不正确1");
throw new TestException(ResultCodeEnum.PARAM_ERROR);
}
......
}
3) 添加特定异常处理方法
@ExceptionHandler(TestException.class)
@ResponseBody
public Result error(TestException e){
e.printStackTrace();
return Result.error().message(e.getMessage()).code(e.getCode());
}