AOP之AspectJ在android中的解读
Posted 小钟视野
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AOP之AspectJ在android中的解读相关的知识,希望对你有一定的参考价值。
一 前言
在没有接触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在android中的解读的主要内容,如果未能解决你的问题,请参考以下文章