-
2021-06-19 00:05:57
一、通过@ControllerAdvice和@ExceptionHandler实现处理controller层的异常
package com.prison.common.config; import com.prison.common.entity.MyException; import com.prison.common.entity.Result; import com.prison.common.enums.ResultType; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * @author WangJing * @Description 异常处理 * @date 2021/6/18 23:48 */ @Slf4j @ControllerAdvice public class MyControllerAdvice { /** * 全局异常捕捉处理 * @param ex * @return */ @ResponseBody @ExceptionHandler(value = Exception.class) public Result<?> errorHandler(Exception ex) { log.error("errorHandler:{}", ex.toString()); return new Result(ResultType.FAIL, ex.getMessage()); } /** * 拦截捕捉自定义异常 MyException.class * @param ex * @return */ @ResponseBody @ExceptionHandler(value = MyException.class) public Result<?> myErrorHandler(MyException ex) { log.error("myErrorHandler:{}", ex.toString()); return new Result(ex); } }
二、定义自定义异常类
package com.prison.common.entity; import com.prison.common.enums.ResultType; import lombok.Data; /** * @author WangJing * @Description 自定义异常 * @date 2021/6/18 0018 23:50 */ @Data public class MyException extends RuntimeException { private Integer code; private String msg; public MyException(ResultType resultType) { code = resultType.getCode(); msg = resultType.getMsg(); } public MyException(Integer code, String msg) { this.code = code; this.msg = msg; } }
三、统一返回类型
package com.prison.common.entity; import com.prison.common.enums.ResultType; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @ClassName Result * @Description 统一返回类 * @Author WangJing * @Date 2021/6/2 21:54 下午 * @Version V1.0 */ @Data @ApiModel("接口返回对象") public class Result<T> { @ApiModelProperty("接口响应编码") private Integer code; @ApiModelProperty("接口响应信息") private String msg; @ApiModelProperty("接口响应数据") private T data; public Result(ResultType resultType, T data) { code = resultType.getCode(); msg = resultType.getMsg(); this.data = data; } public Result() { code = ResultType.SUCCESS.getCode(); msg = ResultType.SUCCESS.getMsg(); } public Result(ResultType resultType) { code = resultType.getCode(); msg = resultType.getMsg(); } public Result(MyException ex) { code = ex.getCode(); msg = ex.getMsg(); } public Result(Integer code, String msg) { this.code = code; this.msg = msg; } }
四、统一返回枚举类
package com.prison.common.enums; /** * @ClassName ResultType * @Description 返回枚举类 * @Author WangJing * @Date 2021/6/2 22:00 下午 * @Version V1.0 */ public enum ResultType { SUCCESS(200,"请求处理成功!"), FAIL(9001,"请求处理失败!"); private Integer code; private String msg; ResultType(Integer code, String msg) { this.code = code; this.msg = msg; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
五、自定义异常使用:
throw new MyException(ResultType.FAIL);
注:以上内容仅提供参考和交流,请勿用于商业用途,如有侵权联系本人删除!
更多相关内容 -
后端异常统一处理解决方案
2019-02-19 16:06:51详情请查看博客链接:https://blog.csdn.net/byteArr/article/details/87705593 -
springmvc返回json的全局异常统一处理demo
2018-11-28 14:46:49springmvc返回json的全局异常统一处理demo,有问题可以留言。 -
Java异常分类及统一处理详解
2020-09-02 14:20:45主要为大家详细介绍了Java异常分类及统一处理,SpringMVC处理异常支持ajax,web.xml配置异常,感兴趣的小伙伴们可以参考一下 -
SpringBoot逻辑异常统一处理方法
2020-08-25 11:54:29主要介绍了SpringBoot逻辑异常统一处理方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
SpringBoot全局异常统一处理
2020-08-13 22:31:04全局异常统一处理3.自定义SpringBoot错误统一处理3.1 继承AbstractErrorController类3.2 实现ErrorController接口4.Filter过滤器中特殊情况下的错误处理 1.SpringBoot默认错误统一处理机制 在基于SpringBoot的Web...文章目录
1.SpringBoot默认错误统一处理机制
在基于SpringBoot的Web应用中,对于Http请求处理过程中发生的各种错误,如常见的400、404和500等错误,SpringBoot默认提供了一种映射到错误页面
/error
的机制来处理所有的错误,并且该页面也由SpringBoot默认提供,不需要开发者自己编写。该页面会显示请求的错误状态码, 以及一些错误原因和消息,如下图分别为SpringBoot默认提供的404错误和500错误页面:
上述/error
错误页面路径可以理解为SpringBoot默认为我们写了一个模版错误页面,然后默认还写了一个Controller
,该Controller
中包含一个/error
请求地址映射指向该错误页面。当SpringBoot的错误处理机制捕获到请求异常之后,则会将用户的原请求携带上错误信息,然后转发到这个/error
页面,页面再显示错误的相关信息。虽然SpringBoot提供了默认的错误显示页面,但是仅使用该默认错误页面会存在大量的局限性:
- 该页面比较简陋,对于用户而言并不友好;
- 500错误暴露了服务器的详细出错原因,存在严重安全隐患;
- 在前后端分离的项目中,客户端需要的不是页面,而是JSON数据。
2.全局异常统一处理
基于上述SpringBoot默认错误处理机制存在的局限性和问题,SpringBoot中提供了
@ControllerAdvice
和@ExceptionHandler
两个注解来实现专门对服务器500异常进行自定义处理。使用示例如下:@ControllerAdvice public class ExceptionController { @ExceptionHandler(Exception.class) @ResponseBody public Map globalException(HttpServletRequest request, Exception e) { Map<String,Object> map = new HashMap<>(); map.put("code",500); map.put("message",e.getMessage()); return map; } @ExceptionHandler(MyException.class) @ResponseBody public Map myException(HttpServletRequest request, Exception e) { Map<String,Object> map = new HashMap<>(); map.put("code",500); map.put("message",e.getMessage()); return map; } }
@ControllerAdvice
注解表示我们定义的是一个控制器增强类,当其他任何控制器发生异常且异常类型符合@ExceptionHandler
注解中指定的异常类时,原请求将会被拦截到这个我们自定义的控制器方法中。在该方法中,我们可以拿到异常信息,于是便可以自定义该如何处理异常,是返回一个我们自定义的模版错误页面,还是返回JSON数据,这将都由我们根据实际应用场景而自己决定。并且我们还可以自定义异常类处理特殊情况。
另外,
@ExceptionHandler
注解只有一个value
参数,为指定的异常类;@ControllerAdvice
注解查看源码参数发现我们还可以指定需要拦截的控制器所在的包路径
。在业务控制器中模拟发生异常:
@GetMapping("user/{id}") public User findById(@PathVariable("id") Long id) { User user = userService.findById(id); int i = 1/0; return user; }
3.自定义SpringBoot错误统一处理
上述通过注解实现的控制器增强类虽然可以处理所有异常对应的500错误,但是对于404等错误,却没法捕获和处理。
实际上,在上文提到的SpringBoot默认错误处理机制中,完成任务处理的控制器实际上是SpringBoot在自动配置类中注入的
BasicErrorController
对象,该类继承AbstractErrorController
,而AbstractErrorController
又实现了ErrorController
接口。所以其实如果我们自定义一个
BasicErrorController
控制器,则Spring容器将不会再使用默认提供的BasicErrorController
控制器,转而使用我们自定义的错误处理控制器。3.1 继承AbstractErrorController类
自定义我们自己的
BasicErrorController
控制器,可以像默认的BasicErrorController
一样直接继承AbstractErrorController
,甚至可以直接照搬BasicErrorController
的代码,根据自己需求做修改即可。如下示例为我自定义的error
处理方法,能够获取了一些错误的基本信息,对常规的错误处理和日志记录已经足够:@Slf4j @RestController @Slf4j @RestController public class HttpErrorController extends AbstractErrorController { private final static String ERROR_PATH = "/error"; public HttpErrorController(ErrorAttributes errorAttributes) { super(errorAttributes); } @Override public String getErrorPath() { return ERROR_PATH; } @RequestMapping(ERROR_PATH) public Map error(HttpServletRequest request, HttpServletResponse response){ Map<String, Object> attributes = getErrorAttributes(request, true); //获取日志需要的请求url和详细堆栈错误信息 String path = attributes.get("path").toString(); String trace = attributes.get("trace").toString(); log.error("path:{} trace:{}",path, trace); //获取错误时间、状态码和错误描述信息,返回给用户 Date timestamp = (Date) attributes.get("timestamp"); Integer status = (Integer) attributes.get("status"); String error = attributes.get("error").toString(); Map<String, Object> map = new HashMap<>(); map.put("code",status); map.put("message",error); map.put("timestamp",timestamp); return map; } }
3.2 实现ErrorController接口
我们也可以直接实现
ErrorController
类来对默认BasicErrorController
控制器进行替换。但是由于ErrorController
接口只有一个过时了的方法,没有AbstractErrorController
类提供的一些获取错误信息的方法,所以这种方式只能捕获到所有错误,但是不能获取错误的详细信息。@RestController public class HttpErrorController implements ErrorController { private final static String ERROR_PATH = "/error"; @Override public String getErrorPath() { return ERROR_PATH; } @RequestMapping(ERROR_PATH) public Map error(HttpServletRequest request, HttpServletResponse response){ Map<String,Object> map = new HashMap<>(); map.put("code","4xx"); map.put("message","请求错误~"); return map; } }
实现
ErrorController
接口需要实现getErrorPath()
方法,返回的路径表示服务器将会重定向到该路径对应的控制器类,本例中为error
方法。测试404错误示例效果:
因此对于这种方式,一般推荐和上文第一种@ControllerAdvice
+@ExceptionHandler
注解的方式结合起来使用:- 这样
@ControllerAdvice
声明的增强控制器专门负责对服务器内部异常的500错误进行处理; - 而实现了
ErrorController
接口的这个错误处理控制器专门处理增强控制器不能捕获到的其他404等错误。
这两种方式一起使用并不会冲突,
@ControllerAdvice
声明的增强控制器会优先捕获异常,不能捕获的部分再由ErrorController
接口的实现类处理即可。4.Filter过滤器中特殊情况下的错误处理
上述的方式看上去已经可以处理几乎所有的错误了。但是,由于上述捕获错误方式原理是在控制器,即本质是在Servlet中,所以如果错误是发生在
Filter
过滤器中,那么错误将可能没法捕获和处理,因为过滤器处理请求的顺序是优先Servlet的,如果在过滤器中讲请求拦截了中断了,则后续SpringBoot中的错误处理机制将无法捕获错误和处理。所以对于过滤器
Filter
中的部分错误,仍需要自己手动根据实际需求处理。 -
SpringBoot 统一异常处理详解
2020-08-26 01:13:04主要介绍了SpringBoot统一异常处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
异常统一处理实例
2016-02-01 18:53:46异常统一处理实例,实例详解请访问博主博客:http://blog.csdn.net/u013142781 -
异常统一处理.zip
2019-07-11 22:20:25Java项目中的异常问题,统一自己写异常的类进行处理。 -
springboot 多模块 集成mybatis redis 日志 异常统一处理,切面拦截器
2018-12-20 15:42:50springboot多模块项目,集成了mybatis,连接池,redis,日志,sql日志打印,异常统一处理,统一返回格式,mapper文件自动生成,generator xml ,切面日志和拦截器,sql注入过滤,解压即可部署打包启动,包含数据库... -
详解Spring MVC/Boot 统一异常处理最佳实践
2020-08-26 12:19:31在 Web 开发中, 我们经常会需要处理各种异常,这篇文章主要介绍了详解Spring MVC/Boot 统一异常处理最佳实践,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 -
Spring Boot统一异常处理最佳实践(拓展篇)
2020-08-26 09:03:14主要给大家介绍了关于Spring Boot统一异常处理最佳实践(拓展篇)的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
Springboot项目全局异常统一处理
2018-06-01 16:51:13最近在做项目时需要对异常进行全局统一处理,主要是一些分类...在网络上找到关于Springboot全局异常统一处理的文档博客主要是两种方案: 1、基于@ControllerAdvice注解的Controller层的全局异常统一处理 以下是...最近在做项目时需要对异常进行全局统一处理,主要是一些分类入库以及记录日志等,因为项目是基于Springboot的,所以去网络上找了一些博客文档,然后再结合项目本身的一些特殊需求做了些许改造,现在记录下来便于以后查看。
在网络上找到关于Springboot全局异常统一处理的文档博客主要是两种方案:
1、基于@ControllerAdvice注解的Controller层的全局异常统一处理
以下是网上一位博主给出的代码示例,该博客地址为:https://www.cnblogs.com/magicalSam/p/7198420.html
import org.springframework.ui.Model; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; /** * controller 增强器 * * @author sam * @since 2017/7/17 */ @ControllerAdvice public class MyControllerAdvice { /** * 全局异常捕捉处理 * @param ex * @return */ @ResponseBody @ExceptionHandler(value = Exception.class) public Map errorHandler(Exception ex) { Map map = new HashMap(); map.put("code", 100); map.put("msg", ex.getMessage()); return map; } /** * 拦截捕捉自定义异常 MyException.class * @param ex * @return */ @ResponseBody @ExceptionHandler(value = MyException.class) public Map myErrorHandler(MyException ex) { Map map = new HashMap(); map.put("code", ex.getCode()); map.put("msg", ex.getMsg()); return map; } }
这个代码示例写的非常浅显易懂,但是需要注意的是:基于@ControllerAdvice注解的全局异常统一处理只能针对于Controller层的异常,意思是只能捕获到Controller层的异常,在service层或者其他层面的异常都不能捕获。
根据这段示例代码以及结合项目本身的实际需求,对该实例代码做了稍微改造(其实几乎没做改造,只是业务处理不一样而已):
@ControllerAdvice public class AdminExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(AdminExceptionHandler.class); /** * @Author: gmy * @Description: 系统异常捕获处理 * @Date: 16:07 2018/5/30 */ @ResponseBody @ExceptionHandler(value = Exception.class) public APIResponse javaExceptionHandler(Exception ex) {//APIResponse是项目中对外统一的出口封装,可以根据自身项目的需求做相应更改 logger.error("捕获到Exception异常",ex); //异常日志入库 return new APIResponse(APIResponse.FAIL,null,ex.getMessage()); } /** * @Author: gmy * @Description: 自定义异常捕获处理 * @Date: 16:08 2018/5/30 */ @ResponseBody @ExceptionHandler(value = MessageCenterException.class)//MessageCenterException是自定义的一个异常 public APIResponse messageCenterExceptionHandler(MessageCenterException ex) { logger.error("捕获到MessageCenterException异常",ex.getException()); //异常日志入库 return ex.getApiResponse(); } }
public class MessageCenterException extends RuntimeException { public MessageCenterException(APIResponse apiResponse, Exception exception){ this.apiResponse = apiResponse; this.exception = exception; } private Exception exception; private APIResponse apiResponse; public Exception getException() { return exception; } public void setException(Exception exception) { this.exception = exception; } public APIResponse getApiResponse() { return apiResponse; } public void setApiResponse(APIResponse apiResponse) { this.apiResponse = apiResponse; } }
经过测试发现可以捕获到Controller层的异常,当前前提是Controller层没有对异常进行catch处理,如果Controller层对异常进行了catch处理,那么在这里就不会捕获到Controller层的异常了,所以这一点要特别注意。
在实际测试中还发现,如果在Controller中不做异常catch处理,在service中抛出异常(service中也不错异常catch处理),那么也是可以在这里捕获到异常的。
2、基于Springboot自身的全局异常统一处理,主要是实现ErrorController接口或者继承AbstractErrorController抽象类或者继承BasicErrorController类
以下是网上一位博主给出的示例代码,博客地址为:https://blog.csdn.net/king_is_everyone/article/details/53080851
@Controller @RequestMapping(value = "error") @EnableConfigurationProperties({ServerProperties.class}) public class ExceptionController implements ErrorController { private ErrorAttributes errorAttributes; @Autowired private ServerProperties serverProperties; /** * 初始化ExceptionController * @param errorAttributes */ @Autowired public ExceptionController(ErrorAttributes errorAttributes) { Assert.notNull(errorAttributes, "ErrorAttributes must not be null"); this.errorAttributes = errorAttributes; } /** * 定义404的ModelAndView * @param request * @param response * @return */ @RequestMapping(produces = "text/html",value = "404") public ModelAndView errorHtml404(HttpServletRequest request, HttpServletResponse response) { response.setStatus(getStatus(request).value()); Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)); return new ModelAndView("error/404", model); } /** * 定义404的JSON数据 * @param request * @return */ @RequestMapping(value = "404") @ResponseBody public ResponseEntity<Map<String, Object>> error404(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)); HttpStatus status = getStatus(request); return new ResponseEntity<Map<String, Object>>(body, status); } /** * 定义500的ModelAndView * @param request * @param response * @return */ @RequestMapping(produces = "text/html",value = "500") public ModelAndView errorHtml500(HttpServletRequest request, HttpServletResponse response) { response.setStatus(getStatus(request).value()); Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)); return new ModelAndView("error/500", model); } /** * 定义500的错误JSON信息 * @param request * @return */ @RequestMapping(value = "500") @ResponseBody public ResponseEntity<Map<String, Object>> error500(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)); HttpStatus status = getStatus(request); return new ResponseEntity<Map<String, Object>>(body, status); } /** * Determine if the stacktrace attribute should be included. * @param request the source request * @param produces the media type produced (or {@code MediaType.ALL}) * @return if the stacktrace attribute should be included */ protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) { ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace(); if (include == ErrorProperties.IncludeStacktrace.ALWAYS) { return true; } if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) { return getTraceParameter(request); } return false; } /** * 获取错误的信息 * @param request * @param includeStackTrace * @return */ private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { RequestAttributes requestAttributes = new ServletRequestAttributes(request); return this.errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace); } /** * 是否包含trace * @param request * @return */ private boolean getTraceParameter(HttpServletRequest request) { String parameter = request.getParameter("trace"); if (parameter == null) { return false; } return !"false".equals(parameter.toLowerCase()); } /** * 获取错误编码 * @param request * @return */ private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request .getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } try { return HttpStatus.valueOf(statusCode); } catch (Exception ex) { return HttpStatus.INTERNAL_SERVER_ERROR; } } /** * 实现错误路径,暂时无用 * @see ExceptionMvcAutoConfiguration#containerCustomizer() * @return */ @Override public String getErrorPath() { return ""; } }
该示例写的也是非常简单明了的,但是结合本身项目的实际需求,也是不能直接拿来用的,需要做相应的改造,改造主要有以下方面:
1、因为项目是前后端分离的,所以Controller层不会有ModelAndView返回类型,需要返回自身的APIResponse返回类型
2、项目需要统计全部的异常,而不只是404或者500的异常
3、捕获到异常之后需要做特殊化的业务处理
所以基于以上几方面对示例代码做了改造,具体改造代码如下:
/** * @Author: gmy * @Description: Springboot全局异常统一处理 * @Date: 2018/5/30 * @Time: 16:41 */ @RestController @EnableConfigurationProperties({ServerProperties.class}) public class ExceptionController implements ErrorController { private ErrorAttributes errorAttributes; @Autowired private ServerProperties serverProperties; /** * 初始化ExceptionController * @param errorAttributes */ @Autowired public ExceptionController(ErrorAttributes errorAttributes) { Assert.notNull(errorAttributes, "ErrorAttributes must not be null"); this.errorAttributes = errorAttributes; } @RequestMapping(value = "/error") @ResponseBody public APIResponse error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new APIResponse(APIResponse.FAIL,null,body.get("message").toString()); } /** * Determine if the stacktrace attribute should be included. * @param request the source request * @param produces the media type produced (or {@code MediaType.ALL}) * @return if the stacktrace attribute should be included */ protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) { ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace(); if (include == ErrorProperties.IncludeStacktrace.ALWAYS) { return true; } if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) { return getTraceParameter(request); } return false; } /** * 获取错误的信息 * @param request * @param includeStackTrace * @return */ private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { RequestAttributes requestAttributes = new ServletRequestAttributes(request); return this.errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace); } /** * 是否包含trace * @param request * @return */ private boolean getTraceParameter(HttpServletRequest request) { String parameter = request.getParameter("trace"); if (parameter == null) { return false; } return !"false".equals(parameter.toLowerCase()); } /** * 获取错误编码 * @param request * @return */ private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request .getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } try { return HttpStatus.valueOf(statusCode); } catch (Exception ex) { return HttpStatus.INTERNAL_SERVER_ERROR; } } /** * 实现错误路径,暂时无用 * @return */ @Override public String getErrorPath() { return ""; } }
经过测试,可以捕获到所有层面上的异常,当前前提仍然是没有对异常进行catch处理,否则这里也是捕获不到
以上为网络上常用的两种全局异常统一处理方案,经过实际测试发现都可以实现满足要求。
其实基于AOP也可以实现异常的全局处理,自己相应的做了测试发现也满足要求,相应的代码如下:
/** * @Author: gmy * @Description: 基于AOP的全局异常统一处理 * @Date: 2018/6/1 * @Time: 13:46 */ @Component @Aspect public class ExceptionAspectController { public static final Logger logger = LoggerFactory.getLogger(ExceptionAspectController.class); @Pointcut("execution(* com.test.test.*.*(..))")//此处基于自身项目的路径做具体的设置 public void pointCut(){} @Around("pointCut()") public Object handleControllerMethod(ProceedingJoinPoint pjp) { Stopwatch stopwatch = Stopwatch.createStarted(); APIResponse<?> apiResponse; try { logger.info("执行Controller开始: " + pjp.getSignature() + " 参数:" + Lists.newArrayList(pjp.getArgs()).toString()); apiResponse = (APIResponse<?>) pjp.proceed(pjp.getArgs()); logger.info("执行Controller结束: " + pjp.getSignature() + ", 返回值:" + apiResponse.toString()); logger.info("耗时:" + stopwatch.stop().elapsed(TimeUnit.MILLISECONDS) + "(毫秒)."); } catch (Throwable throwable) { apiResponse = handlerException(pjp, throwable); } return apiResponse; } private APIResponse<?> handlerException(ProceedingJoinPoint pjp, Throwable e) { APIResponse<?> apiResponse = null; if(e.getClass().isAssignableFrom(MessageCenterException.class) ){ MessageCenterException messageCenterException = (MessageCenterException)e; logger.error("RuntimeException{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + messageCenterException.getException().getMessage() + "}", e); apiResponse = messageCenterException.getApiResponse(); } else if (e instanceof RuntimeException) { logger.error("RuntimeException{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + e.getMessage() + "}", e); apiResponse = new APIResponse(APIResponse.FAIL,null,e.getMessage()); } else { logger.error("异常{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + e.getMessage() + "}", e); apiResponse = new APIResponse(APIResponse.FAIL,null,e.getMessage()); } return apiResponse; } }
经过测试,在执行切点中配置的路径中的方法有异常时,可以被这里捕获到。
以上是自己了解到并且亲自测试可行的全局异常统一处理方案,如果各位博友有什么问题或者有什么新的方案可以一块探讨下
2018/11/28最新编辑
经过一段时间的使用,现在项目里已经统一使用AOP方式来做全局异常统一处理了,选用AOP方式主要是因为AOP不只可以做全局异常统一处理还可以统一打印接口请求入参和返回结果日志,打印接口访问性能日志,处理sql注入攻击以及处理入参特殊字符等问题
下面贴出代码,供大家参考,也仅供参考
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; /** * @Author: gmy * @Description: 调用接口打印性能日志以及接口报错之后记录错误日志 * @Date: 2018/9/20 * @Time: 15:16 */ @Component @Aspect public class InterfaceRequestErrrorAndPerformanceLog { public static final Logger logger = LoggerFactory.getLogger(InterfaceRequestErrrorAndPerformanceLog.class); @Value("${dc.log.bad.value:3000}") private int performanceBadValue; @Resource private RabbitMQService rabbitMQService; @Resource private InterfaceErrorService interfaceErrorService; @Pointcut("execution(* test.test.test.test.test.controller.*.*.*(..))") public void pointCut(){} @Around("pointCut()") public APIResponse handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable{ Stopwatch stopwatch = Stopwatch.createStarted(); APIResponse apiResponse; try { logger.info("执行Controller开始: " + pjp.getSignature() + " 参数:" + Lists.newArrayList(pjp.getArgs()).toString()); //处理入参特殊字符和sql注入攻击 checkRequestParam(pjp); //执行访问接口操作 apiResponse = (APIResponse) pjp.proceed(pjp.getArgs()); try{ logger.info("执行Controller结束: " + pjp.getSignature() + ", 返回值:" + JSONObject.toJSONString(apiResponse)); //此处将日志打印放入try-catch是因为项目中有些对象实体bean过于复杂,导致序列化为json的时候报错,但是此处报错并不影响主要功能使用,只是返回结果日志没有打印,所以catch中也不做抛出异常处理 }catch (Exception ex){ logger.error(pjp.getSignature()+" 接口记录返回结果失败!,原因为:{}",ex.getMessage()); } Long consumeTime = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS); logger.info("耗时:" + consumeTime + "(毫秒)."); //当接口请求时间大于3秒时,标记为异常调用时间,并记录入库 if(consumeTime > performanceBadValue){ DcPerformanceEntity dcPerformanceEntity = new DcPerformanceEntity(); dcPerformanceEntity.setInterfaceName(pjp.getSignature().toString()); dcPerformanceEntity.setRequestParam(Lists.newArrayList(pjp.getArgs()).toString()); dcPerformanceEntity.setConsumeTime(consumeTime + "毫秒"); RabbitMQMessageTarget mqTarget = RabbitMQMessageTarget.createFanoutTarget(ProjectConstants.DC_KEY_EXCHANGE_PERFORMANCE, new String[] { ProjectConstants.DC_KEY_QUEUE_PERFORMANCE}); rabbitMQService.send(mqTarget, JSON.toJSONString(dcPerformanceEntity)); } } catch (Exception throwable) { apiResponse = handlerException(pjp, throwable); } return apiResponse; } /** * @Author: gmy * @Description: 处理接口调用异常 * @Date: 15:13 2018/10/25 */ private APIResponse handlerException(ProceedingJoinPoint pjp, Throwable e) { APIResponse apiResponse; if(e.getClass().isAssignableFrom(ProjectException.class) ){ //ProjectException为自定义异常类,项目中Controller层会把所有的异常都catch掉,并手工封装成ProjectException抛出来,这样做的目的是ProjectException会记录抛出异常接口的路径,名称以及请求参数等等,有助于错误排查 ProjectException projectException = (ProjectException)e; logger.error("捕获到ProjectException异常:",JSONObject.toJSONString(projectException.getDcErrorEntity())); RabbitMQMessageTarget mqTarget = RabbitMQMessageTarget.createFanoutTarget(ProjectConstants.DC_KEY_EXCHANGE_INTERFACE_ERROR, new String[] { ProjectConstants.DC_KEY_QUEUE_INTERFACE_ERROR}); rabbitMQService.send(mqTarget, JSON.toJSONString(dataCenterException.getDcErrorEntity())); apiResponse = new APIResponse(APIResponse.FAIL,null,projectException.getDcErrorEntity().getErrorMessage()); } else if (e instanceof RuntimeException) { logger.error("RuntimeException{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + e.getMessage() + "}", e); apiResponse = new APIResponse(APIResponse.FAIL,null,e.getMessage()); } else { logger.error("异常{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + e.getMessage() + "}", e); apiResponse = new APIResponse(APIResponse.FAIL,null,e.getMessage()); } return apiResponse; } /** * @Author: gmy * @Description: 处理入参特殊字符和sql注入攻击 * @Date: 15:37 2018/10/25 */ private void checkRequestParam(ProceedingJoinPoint pjp){ String str = String.valueOf(pjp.getArgs()); if (!IllegalStrFilterUtil.sqlStrFilter(str)) { logger.info("访问接口:" + pjp.getSignature() + ",输入参数存在SQL注入风险!参数为:" + Lists.newArrayList(pjp.getArgs()).toString()); DcErrorEntity dcErrorEntity = interfaceErrorService.processDcErrorEntity(pjp.getSignature() + "",Lists.newArrayList(pjp.getArgs()).toString(),"输入参数存在SQL注入风险!"); throw new DataCenterException(dcErrorEntity); } if (!IllegalStrFilterUtil.isIllegalStr(str)) { logger.info("访问接口:" + pjp.getSignature() + ",输入参数含有非法字符!,参数为:" + Lists.newArrayList(pjp.getArgs()).toString()); DcErrorEntity dcErrorEntity = interfaceErrorService.processDcErrorEntity(pjp.getSignature() + "",Lists.newArrayList(pjp.getArgs()).toString(),"输入参数含有非法字符!"); throw new DataCenterException(dcErrorEntity); } } }
代码中使用了一些其他的工具类,比如IllegalStrFilterUtil等,我也把代码贴出来
import org.slf4j.LoggerFactory; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @Author: gmy * @Description: 特殊字符检测工具(防止传入非法字符和sql注入攻击) * @Date: 2018/10/25 * @Time: 15:08 */ public class IllegalStrFilterUtil { private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(IllegalStrFilterUtil.class); private static final String REGX = "!|!|@|◎|#|#|(\\$)|¥|%|%|(\\^)|……|(\\&)|※|(\\*)|×|(\\()|(|(\\))|)|_|——|(\\+)|+|(\\|)|§ "; /** * 对常见的sql注入攻击进行拦截 * * @param sInput * @return * true 表示参数不存在SQL注入风险 * false 表示参数存在SQL注入风险 */ public static Boolean sqlStrFilter(String sInput) { if (sInput == null || sInput.trim().length() == 0) { return false; } sInput = sInput.toUpperCase(); if (sInput.indexOf("DELETE") >= 0 || sInput.indexOf("ASCII") >= 0 || sInput.indexOf("UPDATE") >= 0 || sInput.indexOf("SELECT") >= 0 || sInput.indexOf("'") >= 0 || sInput.indexOf("SUBSTR(") >= 0 || sInput.indexOf("COUNT(") >= 0 || sInput.indexOf(" OR ") >= 0 || sInput.indexOf(" AND ") >= 0 || sInput.indexOf("DROP") >= 0 || sInput.indexOf("EXECUTE") >= 0 || sInput.indexOf("EXEC") >= 0 || sInput.indexOf("TRUNCATE") >= 0 || sInput.indexOf("INTO") >= 0 || sInput.indexOf("DECLARE") >= 0 || sInput.indexOf("MASTER") >= 0) { Logger.error("该参数怎么SQL注入风险:sInput=" + sInput); return false; } Logger.info("通过sql检测"); return true; } /** * 对非法字符进行检测 * * @param sInput * @return * true 表示参数不包含非法字符 * false 表示参数包含非法字符 */ public static Boolean isIllegalStr(String sInput) { if (sInput == null || sInput.trim().length() == 0) { return false; } sInput = sInput.trim(); Pattern compile = Pattern.compile(REGX, Pattern.CASE_INSENSITIVE); Matcher matcher = compile.matcher(sInput); Logger.info("通过字符串检测"); return matcher.find(); } }
以上代码中涉及到真实项目信息的内容我都做了相应修改,代码仅供技术交流使用。
-
springBoot异常统一处理
2021-05-17 12:52:25很多时候,我们需要对异常进行...通过借助@RestControllerAdvice注解,可设置全局异常统一处理,并记录日志 @Slf4j @RestControllerAdvice public class DefaultExceptionHandler { /**缺少必要的参数*/ @Exception- 很多时候,我们需要对异常进行捕捉监控,快速获知线上业务异常情况,通常是在controller层try catch进行处理,记录日志
- 这种方式需要每个api单独设定一个异常捕获,造成大量重复工作,界面显示也非常不友好
- 通过借助@RestControllerAdvice注解,可设置全局异常统一处理,并记录日志
@Slf4j @RestControllerAdvice public class DefaultExceptionHandler { /**缺少必要的参数*/ @ExceptionHandler(value = MissingServletRequestParameterException.class) public Result missingParameterHandler(HttpServletRequest request,MissingServletRequestParameterException e) { this.logError(request,e); return Result.fail(ErrorCode.PARAM_MISSING); } /**参数类型不匹配*/ @ExceptionHandler(value = MethodArgumentTypeMismatchException.class) public Result methodArgumentTypeMismatchException(HttpServletRequest request,MethodArgumentTypeMismatchException e) { this.logError(request,e); return Result.fail(ErrorCode.PARAM_TYPE_MISMATCH); } /**不支持的请求方法*/ @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) public Result httpRequestMethodNotSupportedException(HttpServletRequest request,HttpRequestMethodNotSupportedException e) { this.logError(request,e); return Result.fail(ErrorCode.HTTP_REQUEST_METHOD_NOT_SUPPORTED_ERROR); } /**参数错误*/ @ExceptionHandler(value = IllegalArgumentException.class) public Result illegalArgumentException(HttpServletRequest request,IllegalArgumentException e) { this.logError(request,e); return Result.fail(ErrorCode.SERVER_ERROR_PRARM); } /**业务异常处理*/ @ExceptionHandler(value = BusinessException.class) public Result businessException(HttpServletRequest request, BusinessException e) { log.error("path:{}, queryParam:{},errorCode:{} message:{}", request.getRequestURI(), request.getQueryString(), e.getErrorCode(),e.getMsg(), e); Result res = new Result(); res.setCode(e.getErrorCode().getCode()); res.setMsg(e.getMsg()); return res; } /**其他异常统一处理*/ @ExceptionHandler(value = Exception.class) public Result exception(HttpServletRequest request, Exception e) { this.logError(request,e); return Result.fail(ErrorCode.SERVER_ERROR); } /** * 记录错误日志 */ private void logError(HttpServletRequest request, Exception e){ log.error("path:{}, queryParam:{}, errorMessage:{}", request.getRequestURI(), request.getQueryString(), e.getMessage(), e); } }
测试
很赞,除零成功被异常捕获并记录日志
-
flask中主动抛出异常及统一异常处理代码示例
2020-09-20 21:01:20主要介绍了flask中主动抛出异常及统一异常处理代码示例,具有一定借鉴价值,需要的朋友可以参考下 -
springboot定义统一返回格式及全局异常处理
2020-09-04 09:57:25springboot定义统一返回格式及全局异常处理。springboot定义统一返回格式及全局异常处理。 -
Spring Boot处理全局统一异常的两种方法与区别
2020-08-25 22:28:27主要给大家介绍了关于Spring Boot处理全局统一异常的两种方法与区别,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧 -
Spring MVC Controller返回值及异常的统一处理方法
2020-08-25 11:25:15主要给大家介绍了关于Spring MVC Controller返回值及异常的统一处理方法,文中通过示例代码介绍的非常详细,对大家的学习或者使用Spring MVC具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧 -
三. spring mvc 异常统一处理
2019-04-16 01:19:04NULL 博文链接:https://gaojiewyh.iteye.com/blog/1297746 -
Spring Cloud Fegin基本使用、全局异常统一处理
2022-01-28 11:15:55一个庞大的微服务生态,每个服务都是处理自己的业务互不干扰,随着业务的不断扩展,就会涉及到一些服务之间的数据传输、接口共用等场景,本文就介绍了Feign基本使用。 一、Feign用途? 跨服务接口调用,例如:A服务... -
基于spring aop的dubbo异常统一处理
2020-12-23 01:54:55dubbo统一异常处理,调用方只显示封装后的异常。1、返回封装后的Exception2、返回封装后的统一返回信息import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.... -
Retrofit2+Rxjava2网络请求异常统一封装处理
2019-04-15 11:53:38该工具类是对Retrofit2+Rxjava2网络请求异常统一封装处理,将onError(Throwable e)中得异常转化成具体的错误信息。除了提示用户以外,还方便开发者准确定位Bug的所在。 -
使用Spring AOP对异常进行统一处理
2014-11-18 15:42:27我们在捕获到异常并对异常进行处理时可能会遇到如下一些问题: 1.不确定应如何处理这些异常 2.需要记录异常日志时没有记录,或者异常在不同的地方重复记录,使得排错调试不方便 ...无法对某些异常进行统一处理和修改。 -
SpringMVC统一异常处理三种方法详解
2021-01-19 21:46:13这篇文章主要介绍了SpringMVC-...如果能将所有类型的异常处理从各层中解耦出来,这样既保证了相关处理过程的功能单一,又实现了异常信息的统一处理和维护。 幸运的是,Spring MVC 框架支持这样的实现。Spring MVC 统一 -
SpringMVC异常统一处理的思路及实现
2019-01-18 18:54:56springMVC中异常处理的思路: mvc中,controller层调用service层,service层调用dao层,每一层我们都将异常通过throws向上抛出,最终抛给DispatcherServlet去找异常处理器进行处理。 springMVC中的异常处理: 1.目录... -
springmvc 异常统一处理的三种方式详解
2018-08-23 16:20:221 描述 在J2EE项目的开发中,不管是对底层的数据库...那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。下面... -
详解使用Spring MVC统一异常处理实战
2020-08-30 21:52:39本篇文章主要介绍了详解使用Spring MVC统一异常处理实战,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
如何优雅的处理Spring Boot异常信息详解
2020-08-26 02:56:53主要给大家介绍了关于如何优雅的处理Spring Boot异常信息的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧 -
Spring MVC异常统一处理的三种方式
2019-08-21 10:27:51Spring 统一异常处理有 3 种方式,分别为: 使用 @ ExceptionHandler 注解 实现 HandlerExceptionResolver 接口 使用 @controlleradvice 注解 使用 @ ExceptionHandler 注解 使用该注解有一个不好的地方就是:... -
Android开发中的异常统一处理
2017-06-16 16:40:17Android开发中的异常统一处理 实际开发中为了防止程序异常奔溃,而使得开发人员不知道奔溃原因,且影响用户体验:所以我们应该在app中统一处理异常,拦截异常信息,上报服务器。 一自定义异常拦截实现Thread....