最近有这样一个想法,当程序出错时,将请求体上传到 sentry,至于 sentry 的集成,可以参考我以前写的博客:自建 sentry 并与 spring boot 集成,sentry 是一个开源的崩溃跟踪平台吧,类似Android开发中常用的 bugly 等。但是,我遇到了:java.lang.IllegalStateException: getReader() has already been called for this request 这样的错误。这个错误的原因是:ServletRequest 里的 getReader 和 getInputStream 两个方法只能被调用一次,且是二选一,不是每个都可调用一次,是两个加起来只能调用一次,对此,网上有太多的解决方案。而我今天要提出的方案,却并不是重写某些方法等,而是使用 aop,没错,就是切面编程啦。请看如下代码:
@Aspect @Component public class ServiceAspect { public static final String POINT = "execution(public * top.kpromise..mapper..*.*(..))"; @Around(POINT) public Object timeAround(ProceedingJoinPoint joinPoint) throws Throwable { try { joinPoint.proceed(); } catch (Throwable e) { rollBack(joinPoint); throw new KpException(getArgs(joinPoint, joinPoint.getArgs()).toString(), e); } return result; } private void rollBack(ProceedingJoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Annotation[] annotations = signature.getMethod().getAnnotations(); for (Annotation annotation : annotations) { if (annotation instanceof Transactional) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); break; } } } private StringBuilder getArgs(ProceedingJoinPoint joinPoint, Object[] args) { if (args.length <= 0) return new StringBuilder(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); StringBuilder stringBuilder = new StringBuilder(); String[] names = signature.getParameterNames(); int length = args.length; ObjectMapper mapper = new ObjectMapper(); for (int i = 0; i < length; i++) { if (i > 0) { stringBuilder.append(","); } Object arg = args[i]; if (arg == null) continue; try { String value = mapper.writeValueAsString(arg); stringBuilder.append(names[i]).append("=").append(value); }catch (Exception e) { e.printStackTrace(); } } return stringBuilder; } }
这里,使用了 Around 方法,然后又用到了 try catch,使用 try catch 后会导致 controller 里的 事物不回滚,此处,我进行了手动回滚。最后,getArgs 这个方法演示了我如何获取到入参。
你也可以不用再抛异常,直接调用 sentry 的方法进行上传,当然了,如果你喜欢,你也可以抛出异常,丢给 有 @RestControllerAdvice 注解的崩溃处理类来处理。