aop之aspectj在android中的解读(代码片段)

小钟视野 小钟视野     2022-12-04     237

关键词:

一 前言

     在没有接触AOP切面编程时,总觉得它是一门特神奇的,特遥不可及的技术,直到公司做无埋,用hook所有监听器的直男方式,遇到无底洞的大坑之后,才痛定思痛执着了解AOP切面编程。
    对于AOP切面编程的意义,最主要是找到切入点,接下来了解AspectJ框架的一些基本核心概念。

    既然是一个框架,那么就要遵循它的规则

二 核心概念

    <1> Join Point 又名Jpoint

        JPoint跟java基本无异,只不过一些关键字可能有差别,定义的切入点都跟方法很像
        是整个Aspectj的中心,也就是切入点。如何查找正确的切入点?
        答案就是你想要做什么,比如无埋的操作,你要监听所有Onclick(v)那么onClick(v)就是一个jPoint
        被当做切入点的一般有属性、方法(包括构造方法)
         AspectJ库的Jpoint:

    <2>Pointcuts : 

        其实也是Jpoint,只不过是Jpoint的一个强大的子集,可以自定义自己想要的JPoint

          <一>:理解各种Signature:可以理解为切入点的具体条件过滤筛选,匹配上才执行切入点,也就是匹配                                                指定JPoint对应的函数(包括构造函数)
                1.MethodSignature:正常java方法条件筛选.继承于CodeSignature
                       一个MethodSignature的格式为:
                       @注解 访问权限 返回值的类型 包名.函数名(参数) 如:

                        (1) @注解和访问权限(也就是java的修饰符public/private/protect,以及static/final)                                                                 这两个属于可选项。

                            如果不设置它们,则默认都会选择也就是忽略访问权限和注解。
                           以访问权限为例,如果没有设置访问权限作为条件,那么public,private,protect及static、                                                   final的函数都会进行搜索。
                        (2) 返回值类型:就是java函数的返回值类型。如果不限定类型的话,就用*通配符表示
                       (3) 包名.函数名:用于查找匹配的java函数。可以使用通配符,包括*和..以及+号。其中*号用于匹                                                       配除.号之外的任意字符,而..则表示任意子package,+号表示子类。
                        (4) 函数的参数:参数匹配比较简单,主要是java的参数类型,比如:
                             (int, char):表示参数只有两个,并且第一个参数类型是int,第二个参数类型是char

                            (String, ..):表示至少有一个参数。并且第一个参数类型是String,后面参数类型不限。                                                                                在参数匹配中,..代表任意参数个数和类型

                            (Object ...):表示不定个数的参数,且类型都是Object,这里的...不是通配符,而是Java中代                                                                        表不定参数的意思
                        例子:
                        java.*.Date:可以表示java.sql.Date,也可以表示java.util.Date
                        Test*:可以表示TestBase,也可以表示TestDervied
                        java..*:表示java任意子类
                        java..*Model+:表示Java任意package中名字以Model结尾的子类,比如TabelModel,                                                                                    TreeModel 等

                2.ConstructorSignature:java构造方法条件筛选:与MethodSignature的类似,但构造方法没有                                                                                     返回类型

                    格式例子:
                    public *..TestDerived.new(..): 区别于MethodSignature的是多了一个new,这是构造方法条件                                                                       筛选必须的,放在最后
                        (1)public:选择public访问权限
                        (2)*..代表任意包名
                        (3)TestDerived.new:代表TestDerived的构造函数
                        (4)(..):代表参数个数和类型都是任意

                3.TypeSinature:java类条件筛选,针对的是整个类

                4.FieldSignature:对于java属性的条件筛选

                   标准格式:
                        @注解 访问权限 类型 类名.成员变量名
                        (1)@注解和访问权限是可选的
                        (2)类型:成员变量类型,*代表任意类型
                        (3)类名.成员变量名:成员变量名可以是*,代表任意成员变量
                       例子:int test..TestBase.base  省略的访问权限,访问test包开头,类名为TestBase且属性为base的                                                         属性

         <二>执行切入点:

            比如是调用方法是执行还是执行方法时执行、设置属性时执行还是获取属性时执行等等

             1.execute(CodeSignature):执行方法体时执行这个切入点
             2.call(CodeSignature):调用方法时执行这个切入点
             3.set(FieldSignature):为属性设置值时执行这个切入点:如 field = "test";
             4.get(FieldSignature):获取属性值时执行这个切入点:如 i = field;
             5.staticinitializaton:静态代码块执行时执行这个切入点 如:static
             6.inittialization:执行构造方法初始化是执行这个切入点 如:new xxx()
             7.handler(NullPointerException):表示catch到NullPointerException的JPoint。
             8.within(TypePattern):TypePattern标示package或者类。表示某个类内。TypePatter可以使用通配符
               结合注解可以表示:被注解的类,符合切入点
             9.target()、this() 注意:this()和target()匹配的时候不能使用通配符。

               等等如下图            

         通过执行切入点可以看出,<一>和<二>除了7、8、9都是要搭配使用的。

         Jpoint的pointCuts如下图:


    <3>.Advice:

           advice就是一种Hook。可以设置在切入点JPoint之前还是之后,执行我们需要添加的代码。

          Advice类型如下:

             1.before(Jpoint):在Jpoint之前执行
             2.after(Jpoint):在jpoint之后执行
            3.around(JPoint):在jpoint中使用proceed()来替代原来方法,如果调用proceed(),则调用的是原始方                                              法,否则原始方法不被调用。

           Advice类型如下:


三.实例详解

1.导入AspectJ库

     compile 'org.aspectj:aspectjtools:1.8.6'
     compile 'org.aspectj:aspectjrt:1.8.6'

2.制作Aspectj编译插件

  本例使用本地插件,不上传仓库。使用as创建插件规则以及上传仓库,晚点讲解。这里有篇博客讲的很好,可以先了解

  https://juejin.im/entry/577bc26e165abd005530ead8

本地插件,结构如下:

也是类似于module


MyPlugin代码如下:

public class MyPlugin implements Plugin<Project> 

    void apply(Project project) 
        println "dddd******************d"

        def hasApp = project.plugins.withType(AppPlugin)
        def hasLib = project.plugins.withType(LibraryPlugin)
        if (!hasApp && !hasLib) 
            throw new IllegalStateException("'android' or 'android-library' plugin required.")
        

        final def log = project.logger
        final def variants
        if (hasApp) 
            variants = project.android.applicationVariants//当前module是app
         else 
            variants = project.android.libraryVariants//当前module是library
        

        project.dependencies 
            // TODO this should come transitively
            compile 'org.aspectj:aspectjrt:1.8.6'//当前module添加aspect库
        

        variants.all  variant ->

            JavaCompile javaCompile = variant.javaCompile
            //JavaCompile编译任务添加lastAction,当前module编译完之后会执行这个action,所以要在每个module中都要执行这个插件
            //因为project是代表当前module中的project。可以学习gradle,敬请期待
            javaCompile.doLast 
                String[] args = ["-showWeaveInfo",
                                 "-1.5",
                                 "-inpath", javaCompile.destinationDir.toString(),
                                 "-aspectpath", javaCompile.classpath.asPath,
                                 "-d", javaCompile.destinationDir.toString(),
                                 "-classpath", javaCompile.classpath.asPath,
                                 "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
                log.debug "ajc args: " + Arrays.toString(args)

                MessageHandler handler = new MessageHandler(true);
                new Main().run(args, handler);//命令执行入口
                
            
        

    

代码很简单,相信一眼就能看明白

这里一定要注意。module中使用到的AspectJ切入点一定要在当前build.gradle中引入这个插件,否则无法生效。

引入方式:apply plugin: 'com.hc.gradle'//执行插件com.hc.gradle.MyPlugin的apply方法,一定要执行编译AspectJ的插件,否则无法在编译期间编译当前module的AspectJ


3.写一个Hugo类,使用@AspectJ注解形式,使用AspectJ实现切面编程。

   重点来了,上代码

@Aspect
public class Hugo 
    public static String TAG = "Hugo";
    @Pointcut("within(@com.example.aoplib.DebugLog *)")//带有注解类DebugLog修饰的类的所有Jpoint
    public void withinAnnotatedClass() //注解DebugLog修饰的类,所有Jpoint(方法中不一定有DebugLog修饰)
        Log.d(TAG,"=====withinAnnotatedClass");//日志不会被打印
    

    @Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")
    public void methodInsideAnnotatedType() //非java关键字synthetic修饰且带有注解DebugLog修饰的类的所有方法
    

    @Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")
    public void constructorInsideAnnotatedType() //执行构造方法且有注解DebugLog修饰
    

    @Pointcut("execution(@com.example.aoplib.DebugLog * *(..)) || methodInsideAnnotatedType()")
    public void method() //被DebugLog注解修饰的所有方法或者被DebugLog注解的类中所有的Jpoint(可根据PointCut的条件)
        Log.d(TAG,"=====method");//日志不会被打印
    

    @Pointcut("execution(@com.example.aoplib.DebugLog *.new(..)) || constructorInsideAnnotatedType()")
    public void constructor() //被DebugLog注解修饰的所有构造方法或者被DebugLog注解的类中所有的Jpoint(可根据PointCut的条件)
        Log.d(TAG,"=====constructor");
    

    @Around("method() || constructor()")
    public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable 
        enterMethod(joinPoint);

        long startNanos = System.nanoTime();
        Object result = "不执行原始方法,原始方法的log不会被打印";
        if ("123".equals("234")) 
            //调用原始方法并将结果返回和字符串拼接,也就是说可以篡改返回值
            //如果不调用此方法,则原始方法就不会被触发
            result = joinPoint.proceed()+"结果已被篡改";
        
//        result = joinPoint.proceed()+"结果已被篡改";
        long stopNanos = System.nanoTime();
        long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);

        exitMethod(joinPoint, result, lengthMillis);

        return result;
    

    private static void enterMethod(JoinPoint joinPoint) 

        //切入点获取切入类型
        CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
        //获取切入点的所在的类:方法所在的类
        Class<?> cls = codeSignature.getDeclaringType();
        //方法名
        String methodName = codeSignature.getName();
        //方法参数名
        String[] parameterNames = codeSignature.getParameterNames();
        //参数值
        Object[] parameterValues = joinPoint.getArgs();

        StringBuilder builder = new StringBuilder("enterMethod \\u21E2 ");
        builder.append(methodName).append('(');
        for (int i = 0; i < parameterValues.length; i++) 
            if (i > 0) 
                builder.append(", ");
            
            builder.append(parameterNames[i]).append('=');
            builder.append(Strings.toString(parameterValues[i]));
        
        builder.append(')');

        if (Looper.myLooper() != Looper.getMainLooper()) 
            builder.append(" [Thread:\\"").append(Thread.currentThread().getName()).append("\\"]");
        

        Log.d(asTag(cls), builder.toString());

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) 
            final String section = builder.toString().substring(2);
            Trace.beginSection(section);
        
    

    private static void exitMethod(JoinPoint joinPoint, Object result, long lengthMillis) 

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) 
            Trace.endSection();
        

        Signature signature = joinPoint.getSignature();
        //方法所在的类
        Class<?> cls = signature.getDeclaringType();
        //方法名
        String methodName = signature.getName();
        //方法返回类型
        boolean hasReturnType = signature instanceof MethodSignature
                && ((MethodSignature) signature).getReturnType() != void.class;

        StringBuilder builder = new StringBuilder("exitMethod \\u21E0 ")
                .append(methodName)
                .append(" [")
                .append(lengthMillis)
                .append("ms]");

        if (hasReturnType) 
            builder.append(" = ");
            builder.append(Strings.toString(result));
        

        Log.d(asTag(cls), builder.toString());
    

    private static String asTag(Class<?> cls) 
        if (cls.isAnonymousClass()) 
            return asTag(cls.getEnclosingClass());//若是匿名内部类则直接查找外部类
        
        return cls.getSimpleName();//外部类名
    

代码还是很简单明了的,主要如下:

(1).使用@AspectJ注解方式表明这个类是一个AspectJ类,主要用于各种切入点

(2).切入点使用@pointCuts注解方式书写筛选条件,代码有注释应该一眼就能看明白

(3).Advice的@Around()结合pointCuts的方法,实现具体切入点

(4)使用@Around()注解的方法实现hook机制

 以上代码是实现应该比较好理解。结合前面的Jpoint讲解,应该是比较好理解。   

4.build之后,AspectJ已经实现插入,运行程序。

 如:

 原始代码:


build之后的class:


很明显,AspectJ把它给拦截,然后先执行Hugo.logAndExecute()方法,这个就是Hugo中@Around注解的方法。

AspectJDemo

扩展:结合注解的方式,AspectJ屡试不爽,比如权限框架、去除线上日志、无埋监听、handler方法错误日志统计等等

主要还是查看AspectJ的文档:

语法大全

官方文档

此文章及实例讲解借鉴博客如下:

https://blog.csdn.net/innost/article/details/49387395

aop之@aspectj技术原理详解

...要功能2主要目标3适用对象4AOP与OOP的关系二Android中使用AspectJ1Gradle配置示2基本概念21切面Aspect22连接点JoinPoint23切点PointCut24通知Advise3执原31BeforeAfterAfterThrowing插入示意图32Around替换逻辑示意图33代码分析4AspectJ切面编写41日志打印42... 查看详情

aspectj在android中的使用攻略(代码片段)

...aspect-orientedprogramming),指的是面向切面编程。而AspectJ是实现AOP的其中一款框架,内部通过处理字节码实现代码注入。AspectJ从2001年发展至今,已经非常成熟稳定,同时使用简单是它的一大优点。至于它的使用... 查看详情

aspectj在android中的使用攻略(代码片段)

...aspect-orientedprogramming),指的是面向切面编程。而AspectJ是实现AOP的其中一款框架,内部通过处理字节码实现代码注入。AspectJ从2001年发展至今,已经非常成熟稳定,同时使用简单是它的一大优点。至于它的使用... 查看详情

spring-aop@aspectj切点函数之@annotation()(代码片段)

...注解请参考Java-Java5.0注解解读packagecom.xgj.aop.spring.advisor.aspectJ.function;importjava.lang.annotation.Documented;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/*****@ClassNam... 查看详情

aop之aspectj-代码注入

AOP之AspectJ-代码注入AOP之AspectJ-代码注入一AOP简介1什么是AOP编程2使用场景3工具和库二AspectJ1简介2一些专业术语3基础知识4AspectJ使用配置三使用场景1一个简单的示例添加相关依赖和声明定义一个注解定义代码注入Aspect类在MainActivi... 查看详情

aop面向切面编程androidstudio中配置aspectj(下载并配置as中jar包|配置gradle和gradle插件版本|配置gradle构建脚本)(代(代码片段)

文章目录一、AspectJ下载二、拷贝aspectjrt.jar到AndroidStudio三、配置Gradle和Gradle插件版本四、配置Gradle构建脚本一、AspectJ下载首先,参考【AOP面向切面编程】AOP简介(AspectJ简介|AspectJ下载)三、AspectJ下载博客,下载AspectJ;下载地址为https:/... 查看详情

aop之注解处理器apt在android中的finderview实际详解(代码片段)

一前言     android中现今流行的各大框架,比如ButterFly、eventBus、OrmLite、Retrofit等都使用注解,注解是什么呢?注解就是元数据,可以理解为属性、方法、类等的一个说明,具体详解可百度,也可移步我... 查看详情

spring核心之aop(代码片段)

AOP的动态代理、AOP框架AspectJ 1、什么是AOP  在软件业,AOP为AspectOrientedProgramming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个... 查看详情

一起学spring之注解和schema方式实现aop

...置的方式来实现AOP,在实现注解方式AOP之前,先了解一下AspectJ。AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP语法,能够在编译时实现代码的注入。Spring通过集成ApsectJ实现了以注解方式定义通知类,大大减少了配... 查看详情

spring中的aop——aspectj的基本使用(代码片段)

AOP(AspectOrientProgramming),也就是面向切面编程,作为面向对象编程的一种补充,当前已经成为一种比较成熟的编程思想,其实AOP问世的时间并不长,甚至在国内的翻译还不太统一(另有人翻译为“... 查看详情

spring——aop之spring2.0中的配置(代码片段)

...切面,可以通过基于schema的方式,也可以通过@AspectJ注解的方式,这两种方式都提供了完整的AspectJ切入点语言中的通知和使用方法,但是依然使用的是SpringAOP的织入方式,也就是通过代理的方式进行织入... 查看详情

spring——aop之spring2.0中的配置(代码片段)

...切面,可以通过基于schema的方式,也可以通过@AspectJ注解的方式,这两种方式都提供了完整的AspectJ切入点语言中的通知和使用方法,但是依然使用的是SpringAOP的织入方式,也就是通过代理的方式进行织入... 查看详情

springaop基础实战知识------hello!aspectj(使用idea进行编程)

AspectJ:AspectJ是Eclipse基金组织的开源项目,它是Java语言的一个AOP实现,是最早、功能比较强大的AOP实现之一,对整套AOP机制都有较好的实现,很多其他语言的AOP实现也借鉴或者采纳了AspectJ中的很多设计。在Java领域,AspectJ中的... 查看详情

spring详解------aspectj实现aop(代码片段)

1、什么是AspectJ?  AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,也可以说AspectJ是一个基于Java语言的AOP框架。通常我们在使用SpringAOP的时候,都会导入AspectJ的相关jar包。在spring2.0以后,spring新增了对Asp... 查看详情

spring15----aop之通知顺序

...在同一连接点执行,那执行顺序如何确定呢?SpringAOP使用AspectJ的优先级规则来确定通知执行顺序。总共有两种情况:同一切面中通知执行顺序、不同切面中的通知执行顺序。      首先让我们看下1) 同... 查看详情

spring系列之aop分析之对通知方法的执行过程

...ils/80261327我们在上一篇文章中说到了前置通知的方法调用AspectJMethodBeforeAdvice#before,在这个before方法中又调用了invokeAdviceMethod这个方法,invokeAdviceMethod这个方法在AspectJMethodBeforeAdvice的父类AbstractAspectJAdvice中。AbstractAspectJAd... 查看详情

spring繁华的aop王国---第三讲

Spring繁华的AOP王国---第三讲@AspectJ形式的SpringAOP@Aspectj形式的aop使用之先睹为快编程方式织入通过自动代理织入@Aspectj形式的Pointcut1.@Aspectj形式的Pointcut声明方式2.@AspectJ形式Pointcut表达式的标志符号executionwithinthis和t... 查看详情

androidaop编程之aspectj(代码片段)

一、AspectJAspectj是一个AOP框架,也是通过对字节码操作,来实现AOP的,但是与ASM需要开发者直接编写操作字节码的代码之外,使用Aspectj不需要开发者直接编写操作字节码代码,而是只需要按照规范编写我们需... 查看详情