在没有接触AOP切面编程时,总觉得它是一门特神奇的,特遥不可及的技术,直到公司做无埋,用hook所有监听器的直男方式,遇到无底洞的大坑之后,才痛定思痛执着了解AOP切面编程。
对于AOP切面编程的意义,最主要是找到切入点,接下来了解AspectJ框架的一些基本核心概念。
既然是一个框架,那么就要遵循它的规则
其实也是Jpoint,只不过是Jpoint的一个强大的子集,可以自定义自己想要的JPoint
<一>:理解各种Signature:可以理解为切入点的具体条件过滤筛选,匹配上才执行切入点,也就是匹配 指定JPoint对应的函数(包括构造函数)(1) @注解和访问权限(也就是java的修饰符public/private/protect,以及static/final) 这两个属于可选项。
如果不设置它们,则默认都会选择也就是忽略访问权限和注解。(String, ..):表示至少有一个参数。并且第一个参数类型是String,后面参数类型不限。 在参数匹配中,..代表任意参数个数和类型
(Object ...):表示不定个数的参数,且类型都是Object,这里的...不是通配符,而是Java中代 表不定参数的意思2.ConstructorSignature:java构造方法条件筛选:与MethodSignature的类似,但构造方法没有 返回类型
格式例子:3.TypeSinature:java类条件筛选,针对的是整个类
4.FieldSignature:对于java属性的条件筛选
标准格式:<二>执行切入点:
比如是调用方法是执行还是执行方法时执行、设置属性时执行还是获取属性时执行等等
1.execute(CodeSignature):执行方法体时执行这个切入点等等如下图
通过执行切入点可以看出,<一>和<二>除了7、8、9都是要搭配使用的。Jpoint的pointCuts如下图:
advice就是一种Hook。可以设置在切入点JPoint之前还是之后,执行我们需要添加的代码。
Advice类型如下:
1.before(Jpoint):在Jpoint之前执行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 {
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注解的方法。
扩展:结合注解的方式,AspectJ屡试不爽,比如权限框架、去除线上日志、无埋监听、handler方法错误日志统计等等
主要还是查看AspectJ的文档:
语法大全
官方文档
此文章及实例讲解借鉴博客如下:
https://blog.csdn.net/innost/article/details/49387395