框架手写系列---AspectJ方式实现埋点上传框架

Posted 战国剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了框架手写系列---AspectJ方式实现埋点上传框架相关的知识,希望对你有一定的参考价值。

一、切面编程

AOP、OOP是程序中经常涉及的概念。

OOP--面向对象编程,java、c#等都是面向对象的编程语言,主张万物皆对象。

AOP是一种编程思想,对OOP的有效补充,在具体代码中,可以针对任意方法、属性、类等,做一个切面处理。针对被标记(注解标记)的代码,做额外处理。

AspectJ是AOP思想的实现方式,将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

二、埋点上传

AspectJ埋点上传的原理:在需要埋点的地方,用注解方式,标注一个切入点。后续在@Aspect注解的类中,处理该切入点,处理完成后,将代码织入到原代码中。

@Aspect注解的类,将使用特定的编译器ajc编译,而不再是javac编译。

1、依赖引用

1-1:在最外层的build中,新增以下依赖:

 dependencies 
        classpath 'com.android.tools.build:gradle:3.6.3'
        //以下为新增依赖
        classpath 'org.aspectj:aspectjtools:1.9.1'
        classpath 'org.aspectj:aspectjweaver:1.9.1'


        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    

1-2:在具体工程的build中,新增以下依赖,以下依赖主要用于日志打印。

此处需注意,根据模块的不同,遍历使用不同的属性:

app模块使用:applicationVariants
library模块使用:libraryVariants


import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
def log = project.logger

//app模块使用:applicationVariants
//library模块使用:libraryVariants
android.applicationVariants.all  variant ->
    if (!variant.buildType.isDebuggable()) 
        log.debug("Skipping non-debuggable build type '$variant.buildType.name'.")
        return
    

    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast 
        String[] args = ["-showWeaveInfo",
                         "-1.5",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", android.bootClasspath.join(
                File.pathSeparator)]

        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler)


        for (IMessage message : handler.getMessages(null, true)) 
            switch (message.getKind()) 
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break;
                case IMessage.WARNING:
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            
        
    

2、定义埋点上传需要的注解

本例中共定义了三种注解,分别用于:修饰注解的基础注解、定义切入点注解、参数注解

2-1:BasePoint

该注解,用于埋点的分类。实际埋点可能分为多种类型,如异常埋点、页面进入埋点、点击埋点等。type代表类别名称,typeId代表类别ID。

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BasePoint 
    String type();
    String typeId();

2-2:DataPoint

该注解是实际用于切点的埋点,让aspectJ识别。BasePoint也是修饰该注解(避免在实际应用中,需要多次手写type与typeId)。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@BasePoint(type = "数据埋点",typeId = "1001")
public @interface DataPoint 

    String value() default "";

2-3:Parameter

该注解用于方法参数的注解。埋点时具体上送的内容,通过该参数注解与参数值,组成key-value形式上送

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Parameter 
    String value();

 

3、AspectJ实现方法切面管理

3-1:在具体需要埋点的方法中,在方法上以及方法的参数中加上注解

 @DataPoint
    private static void testPointStaticPrivate(@Parameter("key1") int a, String b) 
        Log.e("MainActivity", "testPointStaticPrivate: "+"方法被执行" );
    

3-2:新建类,用Aspect注解该类。在该类中收集数据并上传

@Aspect
public class AspectManager 
    private static final String TAG = "AspectManager";

    @Pointcut("execution(@com.sunny.aaspect.annotation.DataPoint * *(..))")
    public void uploadDataPoint() 
    

    @Around("uploadDataPoint()")
    public Object handleUploadPoint(ProceedingJoinPoint joinPoint) 

        //获取到方法的反射对象
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法实体
        Method method = signature.getMethod();
        //方法上的所有注解
        Annotation[] annotations = method.getAnnotations();

        //获取方法的接收参数的注解
        Annotation[] methodParamsAnnotations = getMethodParamsAnnotations(method);

        BasePoint basePoint = null;
        for (Annotation annotation : annotations) 
            basePoint = annotation.annotationType().getAnnotation(BasePoint.class);
            if (basePoint == null) 
                break;
            
        

        if (basePoint == null) 
            return sourceMethod(joinPoint);
        
        //获取到基础注解上的类别和类别ID
        String type = basePoint.type();
        String typeId = basePoint.typeId();

        //获取到参数上的key-value
        JSONObject paramsData = getParamsData(methodParamsAnnotations, joinPoint.getArgs());
        Log.e(TAG, "上传数据---->  type:" + type + "   typeId:" + typeId + "   params:" + (paramsData != null ? paramsData.toJSONString() : ""));
        return sourceMethod(joinPoint);
    

    private JSONObject getParamsData(Annotation[] methodParamsAnnotations, Object[] args) 
        JSONObject params = null;
        if (methodParamsAnnotations == null || methodParamsAnnotations.length == 0) 
            return null;
        
        params = new JSONObject();
        int i = 0;
        for (Annotation methodParamsAnnotation : methodParamsAnnotations) 
            if (methodParamsAnnotation instanceof Parameter) 
                params.put(((Parameter) methodParamsAnnotation).value(), JSONObject.toJSONString(args[i++]));
            
        
        return params;
    

    /**
     * 原方法必须执行
     */
    private Object sourceMethod(ProceedingJoinPoint joinPoint) 
        try 
            return joinPoint.proceed();
         catch (Throwable throwable) 
            throwable.printStackTrace();
        
        return null;
    

    /**
     * 获取方法的接收参数的注解
     */
    private Annotation[] getMethodParamsAnnotations(Method method) 
        //一个参数可能有多个注解 Annotation[]
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (parameterAnnotations.length == 0) 
            return null;
        
        Annotation[] result = new Annotation[parameterAnnotations.length];
        int i = 0;
        for (Annotation[] parameterAnnotation : parameterAnnotations) 
            for (Annotation annotation : parameterAnnotation) 
                result[i++] = annotation;
            
        

        return result;
    

至此,完成了AspectJ形式的埋点上传,不侵入原代码,埋点信息不影响原逻辑且有统一的入口,方便后续的修改和扩展。

后续的扩展,只需要在需要埋点的方法或者页面、异常等地方加入注解,并仿造DataPoint的方式,新建AspectJ处理类即可。

 

以上是关于框架手写系列---AspectJ方式实现埋点上传框架的主要内容,如果未能解决你的问题,请参考以下文章

AOP之AspectJ在Android实现无侵入埋点实践

框架手写系列---apt注解处理器方式实现ButterKnife框架

框架手写系列---Asm方式实现日志插入

框架手写系列---apt方式实现ARouter框架

框架手写系列---javassist修改字节码方式,实现美团Robust热修复框架

框架手写系列---通过反射手写EventBus框架