使用Spring AOP+注解实现Cat埋点与异常处理

AOP定义

AOP:面向切面编程,是一种编程思想。利用AOP能够将一系列与业务无关且重复的代码抽取出来。例如安全检查、日志、事务等代码。大量业务代码例如各种service,其实只关心服务本身。但是整个系统是需要关心耗时、错误率等指标的,需要上报数据,像这类无关业务的代码就可以通过AOP思想抽离出来。

简而言之,就是一种方法增强的技术,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。

Spring的AOP实现是基于JVM的动态代理。

Cat埋点

Cat是什么

CAT(Central Application Tracking)是一个实时和接近全量的监控系统,它侧重于对Java应用的监控。是美团开源的项目,点击此处查看Github页面,提供了方便简洁的API,常用的监控对象例如Transaction、Event。

定义注解

创建CatMonitor注解,作用在方法上,带有该注解的方法将会上报Cat埋点数据。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CatMonitor {
}

定义Aspect

定义切入点@annotation(catMonitor),通知方式选择环绕通知Around。

import com.dianping.cat.Cat;
import com.sankuai.meituan.waimai.ad.aspect.annotation.CatMonitor;
import com.dianping.cat.message.Transaction;
import com.dianping.lion.client.spi.Order;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(1)
public class CatMonitorAspect {

    @Around("@annotation(catMonitor)")
    public Object around(ProceedingJoinPoint joinPoint, CatMonitor catMonitor)  throws Throwable {
        // Transaction的类型:定义为类名
        String type = joinPoint.getTarget().getClass().getSimpleName();
        // Transaction的类型:定义为方法名
        String name = ((MethodSignature) joinPoint.getSignature()).getMethod().getName();
        // Cat上报类型为Transaction
        Transaction transaction = Cat.newTransaction(type, name);
        try {
            // 执行原方法
            Object result = joinPoint.proceed();
            // 方法执行成功
            transaction.setStatus(Transaction.SUCCESS);
            return result;
        } catch (Throwable e) {
            // 方法执行异常
            transaction.setStatus(e);
            Cat.logError(e);
            throw e;
        } finally {
            // 上报数据,一定要用complete及时关闭
            transaction.complete();
        }
    }
}

异常处理

类似的,还可以实现异常的统一处理。

定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExceptionHandle {

}

定义Aspect

定义切入点为@annotation(exceptionHandle),通知方式为环绕通知。这里的处理逻辑是当方法上有@ExceptionHandle注解时,该方法执行过程若发生异常,将被捕获,并输出到日志中。

并且在这里使用了Order定义CatMonitor和ExceptionHandle切面的执行顺序,值越小,执行顺序越靠前。同一个方法包含多个切面时,优先级小的切面嵌套在优先级大的切面的内部。

import com.dianping.lion.client.spi.Order;
import com.sankuai.meituan.waimai.ad.aspect.annotation.TryCatchHandle;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;

@Aspect
@Component
@Slf4j
@Order(2)
public class exceptionAspect {

    @Around("@annotation(exceptionHandle)")
    public Object around(ProceedingJoinPoint point, ExceptionHandle exceptionHandle) {
        String methodName = StringUtils.EMPTY;
        Object[] args = null;
        try {
            // 方法签名
            MethodSignature signature = (MethodSignature) point.getSignature();
            // 方法名
            methodName = signature.getMethod().getName();
            // 入参
            args = point.getArgs();
            return point.proceed();
        } catch (Throwable e) {
            // 捕获到异常
            // 使用Optional来安全处理参数数组,避免空指针
            String params = Optional.ofNullable(args)
                    .map(arr -> Arrays.stream(arr)
                    .map(param -> param == null ? "null" : param.toString())
                    .collect(Collectors.joining(", ")))
                    .orElse("no params");
            // 打印日志
            log.error("method_proceed_error, method name {} , params: [{}]", methodName, params, e);
            return null;
        }
    }
}

AOP失效

Spring AOP在某些场景下可能会失效,AOP是基于动态代理实现的,动态代理失效时,AOP自然会失效,即代理不到对象,常见的场景如:

  • 非Spring管理的Bean对象
  • 私有方法
  • 调用静态方法
  • 调用final方法
  • 同一个类中,方法调用另一个方法,此时另一个方法的切面会失效。
  • 调用异步方法:如@Async注解的方法,这类方法是在运行时会创建新线程或使用线程池,AOP无法拦截到。

发表评论

滚动至顶部