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 {
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()

总结
初步可以满足个人的一些日常乐子开发。
更新
- 简化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();
}
|