在日常项目中,咱们难免会遇到体系错误的状况。如果对体系反常的状况不做处理,Springboot自身会默认将错误反常作为接口的请求回来。

@GetMapping("/testNorError")
public void testNorError() {
  try {
    throw new MyException(6000, "我的错误");
   }catch (Exception e){
    throw new MyException(5000, "我的包装反常", e);
   }
}

SpringBoot异常处理?用这两个就够啦!

从上图能够看到,Springboot没有对反常进行处理的状况下,将错误的仓库直接当做呼应数据回来了。这样对用户既不友好,又或许由于泄漏体系仓库信息引发潜在的安全风险。因此,建立一个完善的反常处理机制,关于保护体系健壮性对错常必要的。

通用反常处理

要快速的建立反常处理机制,那么需要考虑怎样对反常进行捕获并加以处理?最快捷的办法便是用 @ExceptionHandler注解完成。

  @ExceptionHandler(MyException.class)
  protected ResponseEntity<Object> handleException(Exception ex) {
    LOGGER.error("Failed to execute,handleException:{}", ex.getMessage(), ex);
    return new ResponseEntity<>(new ResultDTO().fail(ResultCodeEnum.ERROR_SERVER), HttpStatus.OK);
   }

经过在Controller内增加上述的反常处理代码,Springboot就能够将相关的错误信息转义成体系的一致错误处理,进而防止仓库外露。(这里的ResultDTO是体系内自界说的JSON结构,能够依据自己的业务自行修改。)

SpringBoot异常处理?用这两个就够啦!

但是,@ExceptionHandler自身存在一个弊端,便是他效果的规模有必要是Controller,也就意味着有多少个Controller,你的反常处理代码便要重复写多遍,这无疑是低效率的。为了减少重复的代码冗余,@ControllerAdvance就进入了咱们的视野。

@ControllerAdvice
@Slf4j
public class ExtGlobalExceptionHandler {
  
  @ExceptionHandler(Exception.class)
  protected ResponseEntity<Object> handleException(Exception ex) {
    LOGGER.error("Failed to execute,handleException:{}", ex.getMessage(), ex);
    return new ResponseEntity<>(new ResultDTO().fail(ResultCodeEnum.ERROR_SERVER), HttpStatus.OK);
   }
}

简略来说,@ControllerAdvance是一个全局处理的注解,其间的代码会对一切的Controller生效,通常会调配@ExceptionHandler处理反常,由此以来就能够完成只编写一次反常处理办法就能够处理全局反常的状况。

至于@ControllerAdvance和@ExceptionHandler是怎样完成这个奇特的功能的,限于篇幅原因,后续会考虑单独出一篇文章具体介绍。(其实依据名字,不难揣度ControllerAdvance便是一种针关于Controller方针的动态署理罢了。)

个性化反常处理

用了@ControllerAdvance和ExceptionHandler,简直能够处理80%的项目面临的报错处理问题。但是,考虑一下。如果一个项目中出现了多组人同时保护、迭代一个体系的时分(降本增效嘛,懂的都懂),每组人要重视的报错自然会不相同。如A组人只重视报错A,B组人员只重视报错B,那么这种通用的反常处理计划是无法区分隔的。

针关于这种状况,就不得不请出别的一位大佬了,他便是:AOP,针关于动态署理有许多的完成办法和结构,这里咱们直接默认选用SpringBoot的自带AOP结构:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
  <version>2.1.11.RELEASE</version>
</dependency>

不管挑选的AOP完成结构是什么,要选用AOP编码都少不了以下两个过程:

1、界说切点和履行机遇(哪些当地要做增强)

2、界说告诉(要怎样增强)

界说切点和履行机遇

关于Springboot自带的AOP结构,其履行机遇共有以下五个:

增强机遇 增强类型 异同点
@After 后置增强 方针办法履行之后调用增强办法
@Before 前置增强 方针办法履行之前先调用增强办法
@AfterReturning 回来增强 方针办法履行return之后回来成果之前调用增强办法,如果出反常则不履行
@AfterThrowing 反常增强 方针办法履行产生反常调用增强办法,需注意的是,处理后反常依旧会往上抛出,不会被catch。
@Around 盘绕增强 盘绕增强包含前面四种增强,经过一定的try-catch处理,盘绕类型能够代替上述的恣意一种增强。

了解了SpringBoot的动态署理的履行机遇之后,咱们还需要知道其界说切点的办法。结构界说切点的办法主要有两个:

  • 切点表达式
  • 注解

注释

咱们首要介绍注释的正确打开办法。要经过注解来完成自己的AOP,那么首要需要界说一个新的注解。这里我简略界说了一个注解:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
    
  String SERVER_NAME() default "";
​
  String action() default "";
}

在界说了注解以后,将注解界说为办法的入参,并经过@annotation()标示出注解的变量称号,由此就能够完成注解AOP的功能。

//处理注解的当地
@Around(value = "@annotation(name)")
public <T> T test(ProceedingJoinPoint point, MyAnnotation name) throws Throwable {
  String serverName = name.SERVER_NAME();
  //处理反常
  return handlerRpcException(point, serverName);
}
​
//具体代码履行处
@MyAnnotation(SERVER_NAME = "下流体系", action = "操作处理")
public <T> T testFunction() {
  return (T) new ResultDTO<>().success(Boolean.TRUE);
}

切点表达式

Springboot的AOP中,还供给了一种非常强大的完成动态署理切点标示的办法,即切点表达式,其根本形式如下所示:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

注意到modifiers-pattern?、declaring-type-pattern?、throws-pattern?等带着问号的参数都对错必填的。紧接着咱们来逐一介绍上述参数的意义:

  • modifiers-pattern? :修饰符匹配,主要表示的是切点是public/private/protected/default的哪一种。
  • ret-type-pattern:顾名思义,指的是回来值的类型,常见如:void/Boolean/String等
  • declaring-type-pattern? :这个指的是被增强的办法、特点的类路径,如com.example.demo.service.aop.MyAspect等
  • name-pattern(param-pattern) :这个是相对关键的参数,指的是被增强的办法称号以及其对应的参数类型。
  • throws-pattern:throw-pattern见词知意,能够知道它是指的办法所抛出的反常类型。

除了了解了上述的表达式的根本匹配意义以外,还有几个特殊的符号通配指的提一下:

***** :匹配任何数量字符 .. :匹配任何数量字符的重复,如在类型形式中匹配任何数量子包;而在办法参数形式中匹配任何数量参数(0个或者多个参数) + :匹配指定类型及其子类型;仅能作为后缀放在类型形式后边

或许上面的代码和介绍让你一脸懵逼,不要紧,能够简略看下下面两个表达式的意义,你就大致明白他们的意义了:

// 1、代表【回来值恣意】且前缀为【com.example.demo.rpc】的【恣意类下】【恣意称号】的【一切参数】办法
execution(* com.example.demo.rpc.*.*(..))
​
// 2、代表【回来值为Boolean】且位于【com.example.demo.rpc及其子包下】的【恣意称号】的【以String为最终一个入参数】的办法
execution(Boolean com.example.demo.rpc..*(.., String))

借助于切面表达式,咱们能够很自在灵敏地界说出咱们的切点,然后经过AOP完成咱们关于反常的处理

@Pointcut("execution(Boolean com.example.demo.rpc..*(.., String)) || execution(别的一个表达式)")
	private void PointCutOfAnno() {
}
@Around(value = "PointCutOfAnno()")
public <T> T testForAOP(ProceedingJoinPoint point) throws Throwable {
    //处理对应的反常
    return handlerRpcException(point, serverName);
}

总结

本文介绍了两种Springboot下针关于反常处理的编写办法:

一、借助于@ControllerAdvance和@ExceptionHandler完成的通用反常处理办法

二、借助于AOP完成的个性化反常处理机制。

两者其实本质上的完成思路都是相同的,经过对履行代码做动态署理,然后将错误包装起来,到达反常不外漏的效果。在实际业务场景中,办法一简直能够包括80%的反常处理场景。计划二则主要针对一个体系中需要做个性化处理的状况,能够依据具体的业务需要进行挑选。

参考文献

@Pointcut 的 12 种用法,你知道几种?