本文将为您提供关于springBoot全局异常方式处理自定义异常@RestControllerAdvice+@ExceptionHandler的详细介绍,同时,我们还将为您提供关于@Controlle
本文将为您提供关于springBoot 全局异常方式处理自定义异常 @RestControllerAdvice + @ExceptionHandler的详细介绍,同时,我们还将为您提供关于@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常、@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常==》记录、@ControllerAdvice + @ExceptionHandler 全局处理异常、@ControllerAdvice+@ExceptionHandler处理架构异常捕获的实用信息。
本文目录一览:- springBoot 全局异常方式处理自定义异常 @RestControllerAdvice + @ExceptionHandler
- @ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常
- @ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常==》记录
- @ControllerAdvice + @ExceptionHandler 全局处理异常
- @ControllerAdvice+@ExceptionHandler处理架构异常捕获
springBoot 全局异常方式处理自定义异常 @RestControllerAdvice + @ExceptionHandler
前言
本文讲解使用 @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理,可以处理大部分开发中用到的自自定义业务异常处理了,避免在Controller 层进行 try-catch 代码示例地址(代码里面类名稍微有些不同): https://gitee.com/coderLOL/springboot-demos
一、处理思路
- 思路:在sevice业务逻辑层 try{}catch(){} 捕获抛出,经由contorller 层抛到 自定义全局处理类 中处理自定义异常及系统异常。
2、实现方式:使用 @RestControllerAdvice + @ExceptionHandler 注解方式实现全局异常处
二、实现过程
1、@ControllerAdvice 注解定义全局异常处理类 ,@ExceptionHandler 注解声明异常处 理方法。
( 本类方法中用到的 ResponseResultUtil 工具类, ResponseCodeEnum 枚举类,下面步骤中会介绍)
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* @author 路飞
* @date 2018-8-21
* @description 全局异常处理: 使用 @RestControllerAdvice + @ExceptionHandler 注解方式实现全
* 局异常处理
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private final Logger logger = LogManager.getLogger(GlobalExceptionHandler.class);
/**
* @author 路飞
* @date 2018-8-22
* @param e 异常
* @description 处理所有不可知的异常
*/
@ExceptionHandler({Exception.class}) //申明捕获那个异常类
public ResponseResultVO globalExceptionHandler(Exception e) {
this.logger.error(e.getMessage(), e);
return new ResponseResultUtil().error(ResponseCodeEnum.OPERATE_FAIL);
}
/**
* @author 路飞
* @date 2018-8-21
* @param e 异常
* @description 处理所有业务异常
*/
@ExceptionHandler({BaseBusinessException.class})
public ResponseResultVO BusinessExceptionHandler(BaseBusinessException e) {
this.logger.error(e);
return new ResponseResultUtil().error(e.getCode(), e.getMessage());
}
}
2、定义一个用于返回页面结果信息的VO对象类:ResponseResultVO
/**
* @author 路飞
* @date 2018-8-21
* @description 请求响应对象
*/
public final class ResponseResultVO<T> {
/**
* @description 响应码
*/
private int code;
/**
* @description 响应消息
*/
private String message;
/**
* @description 分页对象 (如果用不到,这个可以不写)
*/
private PageVO page;
/**
* @description 数据
*/
private Object data;
public final int getCode() {
return this.code;
}
public final void setCode(int code) {
this.code = code;
}
public final String getMessage() {
return this.message;
}
public final void setMessage( String message) {
this.message = message;
}
public final PageVO getPage() {
return this.page;
}
public final void setPage( PageVO page) {
this.page = page;
}
public final Object getData() {
return this.data;
}
public final void setData(Object data) {
this.data = data;
}
public ResponseResultVO(int code, String message, PageVO page, Object data) {
super();
this.code = code;
this.message = message;
this.page = page;
this.data = data;
}
}
3、 定义一个对步骤2中 返回信息结果处理的工具类:ResponseResultUtil
/**
* @author zhangwenlong
* @date 2018-8-20
* @description 请求响应工具类
*/
public final class ResponseResultUtil {
/**
* @param code 响应码
* @param message 相应信息
* @param any 返回的数据
* @description 请求成功返回对象
*/
public final ResponseResultVO success(int code, String message, PageVO page, Object any) {
return new ResponseResultVO(code, message, page, any);
}
/**
* @param any 返回的数据
* @description 请求成功返回对象
*/
public final ResponseResultVO success(Object any) {
int code = ResponseCodeEnum.SUCCESS.getCode();
String message = ResponseCodeEnum.SUCCESS.getMessage();
return this.success(code, message, null, any);
}
/**
* @param any 返回的数据
* @description 请求成功返回对象
*/
public final ResponseResultVO success(Object any, PageVO page) {
int code = ResponseCodeEnum.SUCCESS.getCode();
String message = ResponseCodeEnum.SUCCESS.getMessage();
return this.success(code, message, page, any);
}
/**
* @description 请求成功返回对象
*/
public final ResponseResultVO success() {
return this.success(null);
}
/**
* @param responseCode 返回的响应码所对应的枚举类
* @description 请求失败返回对象
*/
public final ResponseResultVO error(ResponseCodeEnum responseCode) {
return new ResponseResultVO(responseCode.getCode(), responseCode.getMessage(), null, null);
}
/**
* @param code 响应码
* @param message 相应信息
* @description 请求失败返回对象
*/
public final ResponseResultVO error(int code, String message) {
return new ResponseResultVO(code, message, null, null);
}
}
4、为方便统一管理异常代码和信息之间的关系,建立枚举类: ResponseCodeEnum
/**
* @author 路飞
* @date 2018-8-20
* @description 响应码配置枚举
*/
public enum ResponseCodeEnum {
// 系统通用
SUCCESS(200, "操作成功"),
UNLOGIN_ERROR(233, "没有登录"),
OPERATE_FAIL(666, "操作失败"),
// 用户
SAVE_USER_INFO_FAILED(2001, "保存用户信息失败"),
GET_USER_INFO_FAILED(2002, "保存用户信息失败"),
WECHAT_VALID_FAILED(2003, "微信验证失败"),
GET_USER_AUTH_INFO_FAILED(2004, "根据条件获取用户授权信息失败"),
SAVE_USER_AUTH_INFO_FAILED(2005, "保存用户授权失败");
private Integer code;
private String message;
ResponseCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public final Integer getCode() {
return this.code;
}
public final String getMessage() {
return this.message;
}
}
5、
(1)封装一个基础业务异常类(让所有自定义业务异常类 继承此 基础类):BaseBusinessException
/**
* @author zhangwenlong
* @date 2018-8-21
* @description 价值分析系统所有的 业务异常父类
*/
public class BaseBusinessException extends RuntimeException {
private Integer code;
// 给子类用的方法
public BaseBusinessException(ResponseCodeEnum responseCodeEnum) {
this(responseCodeEnum.getMessage(), responseCodeEnum.getCode());
}
private BaseBusinessException(String message, Integer code) {
super(message);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
(2)自定义的业务异常类 (例如):
自定义用户异常
/**
* @author 路费
* @date 2018-8-21
* @description 自定义用户异常
*/
public class UserException extends BaseBusinessException { // 继承继承 业务异常类
public UserException(ResponseCodeEnum responseCodeEnum) {
super(responseCodeEnum);
}
}
6、service 层抛出
/**
* @author 路飞
* @date 2018-8-21
* @description 用户信息业务接口实现类
*/
@Service("userInfoService")
public class UserInfoSerimpl implements UserInfoService {
private Logger logger = LoggerFactory.getLogger(UserInfoSerimpl.class);
@Resource
private UserInfoMapper userInfoMapper; // maybatis通用Mapper插件
/**
* @author 路飞
* @date 2018-8-21
* @param userInfo 用户信息
* @description 保存用户信息
*/
@Override
public void saveUserInfo(UserInfo userInfo) {
try {
userInfoMapper.insertSelective(userInfo);
} catch (Exception e) {
logger.error("获取用户信息失败", e);
//抛出自定义异常: ResponseCodeEnum.SAVE_USER_INFO_FAILED
throw new UserException(ResponseCodeEnum.SAVE_USER_INFO_FAILED);
}
}
}
后续会提供代码实例及下载地址。。。
@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常
继承 ResponseEntityExceptionHandler 类来实现针对 Rest 接口 的全局异常捕获,并且可以返回自定义格式:
复制代码
1 @Slf4j
2 @ControllerAdvice
3 public class ExceptionHandlerBean extends ResponseEntityExceptionHandler {
4
5 /**
6 * 数据找不到异常
7 * @param ex
8 * @param request
9 * @return
10 * @throws IOException
11 */
12 @ExceptionHandler({DataNotFoundException.class})
13 public ResponseEntity<Object> handleDataNotFoundException(RuntimeException ex, WebRequest request) throws IOException {
14 return getResponseEntity(ex,request,ReturnStatusCode.DataNotFoundException);
15 }
16
17 /**
18 * 根据各种异常构建 ResponseEntity 实体. 服务于以上各种异常
19 * @param ex
20 * @param request
21 * @param specificException
22 * @return
23 */
24 private ResponseEntity<Object> getResponseEntity(RuntimeException ex, WebRequest request, ReturnStatusCode specificException) {
25
26 ReturnTemplate returnTemplate = new ReturnTemplate();
27 returnTemplate.setStatusCode(specificException);
28 returnTemplate.setErrorMsg(ex.getMessage());
29
30 return handleExceptionInternal(ex, returnTemplate,
31 new HttpHeaders(), HttpStatus.OK, request);
32 }
33
34 }
转:
https://www.cnblogs.com/junzi2099/p/7840294.html
spring 处理异常的三种方式:
对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service 层,当数据库操作失败时让 Service 层抛出运行时异常,Spring 事物管理器就会进行回滚。
如此一来,我们的 Controller 层就不得不进行 try-catch Service 层的异常,否则会返回一些不友好的错误信息到客户端。但是,Controller 层每个方法体都写一些模板化的 try-catch 的代码,很难看也难维护,特别是还需要对 Service 层的不同异常进行不同处理的时候。例如以下 Controller 方法代码(非常难看且冗余):
/**
* 手动处理 Service 层异常和数据校验异常的示例
* @param dog
* @param errors
* @return
*/
@PostMapping(value = "")
AppResponse add(@Validated(Add.class) @RequestBody Dog dog, Errors errors){
AppResponse resp = new AppResponse();
try {
// 数据校验
BSUtil.controllerValidate(errors);
// 执行业务
Dog newDog = dogService.save(dog);
// 返回数据
resp.setData(newDog);
}catch (BusinessException e){
LOGGER.error(e.getMessage(), e);
resp.setFail(e.getMessage());
}catch (Exception e){
LOGGER.error(e.getMessage(), e);
resp.setFail("操作失败!");
}
return resp;
}
- 本文讲解使用 @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理,只要设计得当,就再也不用在 Controller 层进行 try-catch 了!而且,@Validated 校验器注解的异常,也可以一起处理,无需手动判断绑定校验结果 BindingResult/Errors 了!
一、优缺点
- 优点:将 Controller 层的异常和数据校验的异常进行统一处理,减少模板代码,减少编码量,提升扩展性和可维护性。
- 缺点:只能处理 Controller 层未捕获(往外抛)的异常,对于 Interceptor(拦截器)层的异常,Spring 框架层的异常,就无能为力了。
二、基本使用示例
2.1 @ControllerAdvice 注解定义全局异常处理类
@ControllerAdvice
public class GlobalExceptionHandler {
}
请确保此 GlobalExceptionHandler 类能被扫描到并装载进 Spring 容器中。
2.2 @ExceptionHandler 注解声明异常处理方法
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
String handleException(){
return "Exception Deal!";
}
}
方法 handleException () 就会处理所有 Controller 层抛出的 Exception 及其子类的异常,这是最基本的用法了。
被 @ExceptionHandler 注解的方法的参数列表里,还可以声明很多种类型的参数,详见文档。其原型如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/**
* Exceptions handled by the annotated method. If empty, will default to any
* exceptions listed in the method argument list.
*/
Class<? extends Throwable>[] value() default {};
}
如果 @ExceptionHandler 注解中未声明要处理的异常类型,则默认为参数列表中的异常类型。所以上面的写法,还可以写成这样:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler()
@ResponseBody
String handleException(Exception e){
return "Exception Deal! " + e.getMessage();
}
}
参数对象就是 Controller 层抛出的异常对象!
三、处理 Service 层上抛的业务异常
有时我们会在复杂的带有数据库事务的业务中,当出现不和预期的数据时,直接抛出封装后的业务级运行时异常,进行数据库事务回滚,并希望该异常信息能被返回显示给用户。
3.1 代码示例
封装的业务异常类:
public class BusinessException extends RuntimeException {
public BusinessException(String message){
super(message);
}
}
Service 实现类:
@Service
public class DogService {
@Transactional
public Dog update(Dog dog){
// some database options
// 模拟狗狗新名字与其他狗狗的名字冲突
BSUtil.isTrue(false, "狗狗名字已经被使用了...");
// update database dog info
return dog;
}
}
其中辅助工具类 BSUtil
public static void isTrue(boolean expression, String error){
if(!expression) {
throw new BusinessException(error);
}
}
那么,我们应该在 GlobalExceptionHandler 类中声明该业务异常类,并进行相应的处理,然后返回给用户。更贴近真实项目的代码,应该长这样子:
/**
* Created by kinginblue on 2017/4/10.
* @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理所有不可知的异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
AppResponse handleException(Exception e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail("操作失败!");
return response;
}
/**
* 处理所有业务异常
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
AppResponse handleBusinessException(BusinessException e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail(e.getMessage());
return response;
}
}
- ontroller 层的代码,就不需要进行异常处理了:
@RestController
@RequestMapping(value = "/dogs", consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE})
public class DogController {
@Autowired
private DogService dogService;
@PatchMapping(value = "")
Dog update(@Validated(Update.class) @RequestBody Dog dog){
return dogService.update(dog);
}
}
3.2 代码说明
Logger 进行所有的异常日志记录。
@ExceptionHandler (BusinessException.class) 声明了对 BusinessException 业务异常的处理,并获取该业务异常中的错误提示,构造后返回给客户端。
@ExceptionHandler (Exception.class) 声明了对 Exception 异常的处理,起到兜底作用,不管 Controller 层执行的代码出现了什么未能考虑到的异常,都返回统一的错误提示给客户端。
备注:以上 GlobalExceptionHandler 只是返回 Json 给客户端,更大的发挥空间需要按需求情况来做。
四、处理 Controller 数据绑定、数据校验的异常
在 Dog 类中的字段上的注解数据校验规则:
@Data
public class Dog {
@NotNull(message = "{Dog.id.non}", groups = {Update.class})
@Min(value = 1, message = "{Dog.age.lt1}", groups = {Update.class})
private Long id;
@NotBlank(message = "{Dog.name.non}", groups = {Add.class, Update.class})
private String name;
@Min(value = 1, message = "{Dog.age.lt1}", groups = {Add.class, Update.class})
private Integer age;
}
说明:@NotNull、@Min、@NotBlank 这些注解的使用方法,不在本文范围内。如果不熟悉,请查找资料学习即可。
其他说明:
@Data 注解是 **Lombok** 项目的注解,可以使我们不用再在代码里手动加 getter & setter。
在 Eclipse 和 IntelliJ IDEA 中使用时,还需要安装相关插件,这个步骤自行Google/Baidu 吧!
Lombok 使用方法见:Java 奇淫巧技之 Lombok
SpringMVC 中对于 RESTFUL 的 Json 接口来说,数据绑定和校验,是这样的:
/**
* 使用 GlobalExceptionHandler 全局处理 Controller 层异常的示例
* @param dog
* @return
*/
@PatchMapping(value = "")
AppResponse update(@Validated(Update.class) @RequestBody Dog dog){
AppResponse resp = new AppResponse();
// 执行业务
Dog newDog = dogService.update(dog);
// 返回数据
resp.setData(newDog);
return resp;
}
使用 @Validated + @RequestBody
注解实现。
当使用了 @Validated + @RequestBody
注解但是没有在绑定的数据对象后面跟上 Errors
类型的参数声明的话,Spring MVC 框架会抛出 MethodArgumentNotValidException
异常。
所以,在 GlobalExceptionHandler 中加上对 MethodArgumentNotValidException
异常的声明和处理,就可以全局处理数据校验的异常了!加完后的代码如下:
/**
* Created by kinginblue on 2017/4/10.
* @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理所有不可知的异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
AppResponse handleException(Exception e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail("操作失败!");
return response;
}
/**
* 处理所有业务异常
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
AppResponse handleBusinessException(BusinessException e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail(e.getMessage());
return response;
}
/**
* 处理所有接口数据验证异常
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
AppResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return response;
}
}
注意到了吗,所有的 Controller 层的异常的日志记录,都是在这个 GlobalExceptionHandler 中进行记录。也就是说,Controller 层也不需要在手动记录错误日志了。
五、总结
本文主要讲 @ControllerAdvice + @ExceptionHandler 组合进行的 Controller 层上抛的异常全局统一处理。
其实,被 @ExceptionHandler 注解的方法还可以声明很多参数,详见文档。
@ControllerAdvice 也还可以结合 @InitBinder、@ModelAttribute 等注解一起使用,应用在所有被 @RequestMapping 注解的方法上,详见搜索引擎。
@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常==》记录
对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service 层抛出运行时异常,Spring 事物管理器就会进行回滚。
如此一来,我们的 Controller 层就不得不进行 try-catch Service 层的异常,否则会返回一些不友好的错误信息到客户端。但是,Controller 层每个方法体都写一些模板化的 try-catch 的代码,很难看也难维护,特别是还需要对 Service 层的不同异常进行不同处理的时候。例如以下 Controller 方法代码(非常难看且冗余):
/**
* 手动处理 Service 层异常和数据校验异常的示例
* @param dog
* @param errors
* @return
*/
@PostMapping(value = "")
AppResponse add(@Validated(Add.class) @RequestBody Dog dog, Errors errors){
AppResponse resp = new AppResponse();
try {
// 数据校验
BSUtil.controllerValidate(errors);
// 执行业务
Dog newDog = dogService.save(dog);
// 返回数据
resp.setData(newDog);
}catch (BusinessException e){
LOGGER.error(e.getMessage(), e);
resp.setFail(e.getMessage());
}catch (Exception e){
LOGGER.error(e.getMessage(), e);
resp.setFail("操作失败!");
}
return resp;
}
本文讲解使用 @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理,只要设计得当,就再也不用在 Controller 层进行 try-catch 了!而且,@Validated 校验器注解的异常,也可以一起处理,无需手动判断绑定校验结果 BindingResult/Errors 了!
一、优缺点
- 优点:将 Controller 层的异常和数据校验的异常进行统一处理,减少模板代码,减少编码量,提升扩展性和可维护性。
- 缺点:只能处理 Controller 层未捕获(往外抛)的异常,对于 Interceptor(拦截器)层的异常,Spring 框架层的异常,就无能为力了。
二、基本使用示例
2.1 @ControllerAdvice 注解定义全局异常处理类
@ControllerAdvice
public class GlobalExceptionHandler {
}
请确保此 GlobalExceptionHandler 类能被扫描到并装载进 Spring 容器中。
2.2 @ExceptionHandler 注解声明异常处理方法
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
String handleException(){
return "Exception Deal!";
}
}
方法 handleException() 就会处理所有 Controller 层抛出的 Exception 及其子类的异常,这是最基本的用法了。
被 @ExceptionHandler 注解的方法的参数列表里,还可以声明很多种类型的参数,详见文档。其原型如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/**
* Exceptions handled by the annotated method. If empty, will default to any
* exceptions listed in the method argument list.
*/
Class<? extends Throwable>[] value() default {};
}
如果 @ExceptionHandler 注解中未声明要处理的异常类型,则默认为参数列表中的异常类型。所以上面的写法,还可以写成这样:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler()
@ResponseBody
String handleException(Exception e){
return "Exception Deal! " + e.getMessage();
}
}
参数对象就是 Controller 层抛出的异常对象!
三、处理 Service 层上抛的业务异常
有时我们会在复杂的带有数据库事务的业务中,当出现不和预期的数据时,直接抛出封装后的业务级运行时异常,进行数据库事务回滚,并希望该异常信息能被返回显示给用户。
3.1 代码示例
封装的业务异常类:
public class BusinessException extends RuntimeException {
public BusinessException(String message){
super(message);
}
}
Service 实现类:
@Service
public class DogService {
@Transactional
public Dog update(Dog dog){
// some database options
// 模拟狗狗新名字与其他狗狗的名字冲突
BSUtil.isTrue(false, "狗狗名字已经被使用了...");
// update database dog info
return dog;
}
}
其中辅助工具类 BSUtil
public static void isTrue(boolean expression, String error){
if(!expression) {
throw new BusinessException(error);
}
}
那么,我们应该在 GlobalExceptionHandler 类中声明该业务异常类,并进行相应的处理,然后返回给用户。更贴近真实项目的代码,应该长这样子:
/**
* Created by kinginblue on 2017/4/10.
* @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理所有不可知的异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
AppResponse handleException(Exception e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail("操作失败!");
return response;
}
/**
* 处理所有业务异常
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
AppResponse handleBusinessException(BusinessException e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail(e.getMessage());
return response;
}
}
Controller 层的代码,就不需要进行异常处理了:
@RestController
@RequestMapping(value = "/dogs", consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE})
public class DogController {
@Autowired
private DogService dogService;
@PatchMapping(value = "")
Dog update(@Validated(Update.class) @RequestBody Dog dog){
return dogService.update(dog);
}
}
3.2 代码说明
Logger 进行所有的异常日志记录。
@ExceptionHandler(BusinessException.class) 声明了对 BusinessException 业务异常的处理,并获取该业务异常中的错误提示,构造后返回给客户端。
@ExceptionHandler(Exception.class) 声明了对 Exception 异常的处理,起到兜底作用,不管 Controller 层执行的代码出现了什么未能考虑到的异常,都返回统一的错误提示给客户端。
备注:以上 GlobalExceptionHandler 只是返回 Json 给客户端,更大的发挥空间需要按需求情况来做。
四、处理 Controller 数据绑定、数据校验的异常
在 Dog 类中的字段上的注解数据校验规则:
@Data
public class Dog {
@NotNull(message = "{Dog.id.non}", groups = {Update.class})
@Min(value = 1, message = "{Dog.age.lt1}", groups = {Update.class})
private Long id;
@NotBlank(message = "{Dog.name.non}", groups = {Add.class, Update.class})
private String name;
@Min(value = 1, message = "{Dog.age.lt1}", groups = {Add.class, Update.class})
private Integer age;
}
说明:@NotNull、@Min、@NotBlank 这些注解的使用方法,不在本文范围内。如果不熟悉,请查找资料学习即可。
其他说明:
@Data 注解是 **Lombok** 项目的注解,可以使我们不用再在代码里手动加 getter & setter。
在 Eclipse 和 IntelliJ IDEA 中使用时,还需要安装相关插件,这个步骤自行Google/Baidu 吧!
Lombok 使用方法见:Java奇淫巧技之Lombok
SpringMVC 中对于 RESTFUL 的 Json 接口来说,数据绑定和校验,是这样的:
/**
* 使用 GlobalExceptionHandler 全局处理 Controller 层异常的示例
* @param dog
* @return
*/
@PatchMapping(value = "")
AppResponse update(@Validated(Update.class) @RequestBody Dog dog){
AppResponse resp = new AppResponse();
// 执行业务
Dog newDog = dogService.update(dog);
// 返回数据
resp.setData(newDog);
return resp;
}
使用 @Validated + @RequestBody
注解实现。
当使用了 @Validated + @RequestBody
注解但是没有在绑定的数据对象后面跟上 Errors
类型的参数声明的话,Spring MVC 框架会抛出 MethodArgumentNotValidException
异常。
所以,在 GlobalExceptionHandler 中加上对 MethodArgumentNotValidException
异常的声明和处理,就可以全局处理数据校验的异常了!加完后的代码如下:
/**
* Created by kinginblue on 2017/4/10.
* @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理所有不可知的异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
AppResponse handleException(Exception e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail("操作失败!");
return response;
}
/**
* 处理所有业务异常
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
AppResponse handleBusinessException(BusinessException e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail(e.getMessage());
return response;
}
/**
* 处理所有接口数据验证异常
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
AppResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return response;
}
}
注意到了吗,所有的 Controller 层的异常的日志记录,都是在这个 GlobalExceptionHandler 中进行记录。也就是说,Controller 层也不需要在手动记录错误日志了。
五、总结
本文主要讲 @ControllerAdvice + @ExceptionHandler 组合进行的 Controller 层上抛的异常全局统一处理。
其实,被 @ExceptionHandler 注解的方法还可以声明很多参数,详见文档。
@ControllerAdvice 也还可以结合 @InitBinder、@ModelAttribute 等注解一起使用,应用在所有被 @RequestMapping 注解的方法上,详见搜索引擎。
@ControllerAdvice + @ExceptionHandler 全局处理异常
本文讲解使用 @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理,只要设计得当,就再也不用在 Controller 层进行 try-catch 了!
一、优缺点
- 优点:将 Controller 层的异常和数据校验的异常进行统一处理,减少模板代码,减少编码量,提升扩展性和可维护性。
- 缺点:只能处理 Controller 层未捕获(往外抛)的异常,对于 Interceptor(拦截器)层的异常,Spring 框架层的异常,就无能为力了。
二、基本使用示例
2.1 @ControllerAdvice 注解定义全局异常处理类
@ControllerAdvice
public class GlobalExceptionHandler {
}
请确保此 GlobalExceptionHandler 类能被扫描到并装载进 Spring 容器中。
2.2 @ExceptionHandler 注解声明异常处理方法
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
String handleException(){
return "Exception Deal!";
}
}
方法 handleException() 就会处理所有 Controller 层抛出的 Exception 及其子类的异常,这是最基本的用法了。
三、处理 Service 层上抛的业务异常
有时我们会在复杂的带有数据库事务的业务中,当出现不和预期的数据时,直接抛出封装后的业务级运行时异常,进行数据库事务回滚,并希望该异常信息能被返回显示给用户。
3.1 代码示例
封装的业务异常类:
public class BusinessException extends RuntimeException {
public BusinessException(String message){
super(message);
}
}
Service 实现类:
@Service
public class DogService {
@Transactional
public Dog update(Dog dog){
// some database options
// 模拟狗狗新名字与其他狗狗的名字冲突
BSUtil.isTrue(false, "狗狗名字已经被使用了...");
// update database dog info
return dog;
}
}
- 其中辅助工具类 BSUtil
public static void isTrue(boolean expression, String error){
if(!expression) {
throw new BusinessException(error);
}
}
那么,我们应该在 GlobalExceptionHandler 类中声明该业务异常类,并进行相应的处理,然后返回给用户。更贴近真实项目的代码,应该长这样子:
/**
* Created by kinginblue on 2017/4/10.
* @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理所有不可知的异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
AppResponse handleException(Exception e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail("操作失败!");
return response;
}
/**
* 处理所有业务异常
* @param e
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
AppResponse handleBusinessException(BusinessException e){
LOGGER.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail(e.getMessage());
return response;
}
}
Controller 层的代码,就不需要进行异常处理了
3.2 代码说明
Logger 进行所有的异常日志记录。
@ExceptionHandler(BusinessException.class) 声明了对 BusinessException 业务异常的处理,并获取该业务异常中的错误提示,构造后返回给客户端。
@ExceptionHandler(Exception.class) 声明了对 Exception 异常的处理,起到兜底作用,不管 Controller 层执行的代码出现了什么未能考虑到的异常,都返回统一的错误提示给客户端。
备注:以上 GlobalExceptionHandler 只是返回 Json 给客户端,更大的发挥空间需要按需求情况来做。
@ControllerAdvice+@ExceptionHandler处理架构异常捕获
1.注解引入
1) @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作为一个@Component,用以定义 @ExceptionHandler/@InitBinder/@ModelAttribute 修饰的方法 , 适用于所有使用 @RequestMapping的方法.
- Spring4之前,@ControllerAdvice 在同一调度的Servlet中协助所有控制器.Spring4之后,@ControllerAdvice支持配置控制器的子集,且默认的行为仍可用
- Spring4之后,@ControllerAdvice通过annotations()/basePackageClasses()/basePackages()方法定制用于选择控制器子集.
2) @ExceptionHandler - 异常处理器
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
Class<? extends Throwable>[] value() default {};
}
单独用@ExceptionHandler,限定当前Controller处理异常.配合@ControllerAdvice则摆脱此限制.
2.使用实例
@ControllerAdvice
public class MultipartExceptionControllerAdvice {
private static final Logger log = LoggerFactory.getLogger(MultipartExceptionControllerAdvice.class);
/**
* 捕获文件上传异常
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = MultipartException.class)
public FrontResult fileErrorHandler(MultipartException ex) {
FrontResult result = new FrontResult();
result.setHasLive(0);
result.setCode(500);
result.setMsg("FAIL.");
log.error("file upload error : " , ex);
return result;
}
}
3.处理Exception在捕获@ResponseStatus修饰的自定义异常时操作
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public String handlerException(HttpServletRequest request , Exception e) throws Exception {
e.printStackTrace();
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
return e.getMessage();
}
}
:::: 如果碰到了某个自定义异常加上了@ResponseStatus,就继续抛出,这样就不会让自定义异常失去加上@ResponseStatus的初衷。
我们今天的关于springBoot 全局异常方式处理自定义异常 @RestControllerAdvice + @ExceptionHandler的分享就到这里,谢谢您的阅读,如果想了解更多关于@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常、@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常==》记录、@ControllerAdvice + @ExceptionHandler 全局处理异常、@ControllerAdvice+@ExceptionHandler处理架构异常捕获的相关信息,可以在本站进行搜索。
本文标签: