某种使用Kotlin的Android运行时AOP

KAop

  没太用过AOP框架,了解了一些在Java界比较有名的AOP框架比如AspectJ,发现大部分都是在编译时处理源代码实现代码的织入。很好奇能不能在运行时实现AOP操作,找了一下确实是有。比如适用于安卓的epic等。
  自己也想整一个类似的,实现用注解标注一个函数就可以Hook这个函数简易运行时框架。所以整了这么一个感觉不太实用的玩具KAop(好歹有那么点用?)。使用Kotlin编写,主要面向Android。改改纯Java/Kotlin程序也可以用~(具体用法随更新会有所改动)
下文:基于KAop使用注解封装XXPermissions的安卓权限请求框架

原理

  用一个接口包装真正的方法的逻辑,再通过创建匿名类获取当前执行的方法(enclosingMethod)并代理执行前面包装的逻辑。在中间通过自定义的切面控制方法该如何调用。缺点是对每一个使用KAop的方法创建两个类,牺牲空间和性能换取AOP。

编写AOP切面

1.继承Aspect,如TimeCostAspect,实现了对方法执行时间的计算。

class TimeCostAspect : Aspect() {
    private var time: Long = 0
    override fun before(point: AbsJoinPoint) {
        time = System.currentTimeMillis()
    }

    override fun after(point: AbsJoinPoint) {
        Log.d("TimeCost", (System.currentTimeMillis() - time).toString() + "ms")
    }
}

2.对于Kotlin调用: 定义一个注解,使用@AspectAnnotation注解该注解,并且指定plugin使用TimeCostAspect
其中order代表切面的执行顺序,越小越先执行。order为一个约定的名称,可以不定义该参数。默认为Int.MAX_VALUE
对于Java调用: 必须要标记注解@Retention(RetentionPolicy.RUNTIME),kotlin默认是RUNTIME。

@AspectAnnotation(plugin = TimeCostAspect::class)
annotation class TimeCost(val order: Int = 0)

使用AOP切面

@NeedToken是定义的某个切面,除了标记注解,在Kotlin只需要额外添加两行代码就可以实现AOP。对在Kotlin中普通函数以及object内的函数,Java的普通方法以及静态方法都可以实现Hook
Kotlin
在需要切面的类初始化,在需要切面的函数标记定义的注解,函数用pointcut{...}包裹一层,其中pointcut是初始化返回的对象,这里override了他的invoke操作。

class KtClassCase {
    private val pointcut = KAop(this)

    @NeedToken
    fun test():String =  pointcut{
        return@pointcut "操作成功"
    }

}

object KtObjectCase {

    private val pointcut = KAop(this)

    @NeedToken
    fun testStatic():String =  pointcut{
        return@pointcut "操作成功"
    }
    
    @NeedToken
    @JvmStatic
    fun testJvmStatic():String = pointcut{
        return@pointcut "操作成功"
    }
}

Java
在Java中使用略显繁琐,这里只是做了个对Java的兼容,建议使用Kotlin。
注意,创建MethodGetter对象时后面的{}
正确:new MethodGetter<…>(…, …){}.proxy();
错误:new MethodGetter<…>(…, …).proxy();

public class JavaCase {
    private final Pointcut pointcut = KAop.inject(this);

    @NeedToken
    public String test(){
        return new MethodGetter<String>(pointcut, ()-> "操作成功"){}.proxy();
    }

    private static final Pointcut pointcutStatic = KAop.inject(JavaCase.class);

    @NeedToken
    public static String testStatic(){
        return new MethodGetter<String>(pointcutStatic, ()-> "操作成功"){}.proxy();
    }
}

Demo

这里模拟了一个需要登录的场景。
切面实现:

public class AuthAspect extends Aspect {
    //模拟token信息
    public static String token = null;
    @Override
    public void before(@NonNull AbsJoinPoint point) {
        Log.d("AuthAspect", "AuthAspect before");
    }

    @Override
    public Object around(@NonNull AbsJoinPoint point) {
        if(token == null){
            Log.d("AuthAspect", "用户权限不足!!");
            return "权限不足";
        } else {
            if("admin".equals(token)){
                Log.d("AuthAspect", "管理员权限");
                return super.around(point) + "@管理员";
            } else {
                Log.d("AuthAspect", "普通权限");
                return super.around(point) + "@用户";
            }
        }
    }

    @Override
    public void after(@NonNull AbsJoinPoint point) {
        Log.d("AuthAspect", "AuthAspect after");
    }
}

点击事件,ktClassCase在上面提到过

val ktClassCase = KtClassCase()
findViewById<Button>(R.id.action).setOnClickListener {
	val result = ktClassCase.test()
	Toast.makeText(this, result, Toast.LENGTH_SHORT).show()

Demo

总结

初步可以满足个人的一些日常乐子开发。

更新

  • 2022/12/19
  1. 简化Java的调用方式
    @NeedToken
    public String test(){
    	return new MethodGetter<String>(pointcut, ()-> "操作成功"){}.proxy();
    }
    
    @NeedToken
    public String test(){
    	return new MethodGetter<String>(){
    		@Override
    		public String proxy() {
    			return pointcut.pointcut(this, ()-> "操作成功");
    		}
    	}.proxy();
    }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值