1. 什么是AOP
AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如Android中的(Log代码、ptag/pv上报代码、监控、权限检查)也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为; 那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手 将这些剖开的切面复原,不留痕迹。
1.1 AOP的横切
AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。在Android App中,哪些是我们需要的横切关注点?个人认为主要包括以下几个方面: Log代码、ptag/pv上报代码、监控、权限检查等。
2.什么是AspectJ?
AspectJ 意思就是Java的Aspect,Java的AOP。它其实不是一个新的语言,它就是一个代码编译器(ajc,后面以此代替),在Java编译器的基础上增加了一些它自己的关键字识别和编译方法。因此,ajc也可以编译Java代码。它在编译期将开发者编写的Aspect程序编织到目标程序中,对目标程序作了重构,目的就是建立目标程序与Aspect程序的连接(耦合,获得对方的引用(获得的是声明类型,不是运行时类型)和上下文信息),从而达到AOP的目的(这里在编译期还是修改了原来程序的代码,但是是ajc替我们做的)。
1、非侵入式监控: 可以在不修监控目标的情况下监控其运行,截获某类方法,甚至可以修改其参数和运行轨迹!
2、学习成本低: 它就是Java,只要会Java就可以用它。
3、功能强大,可拓展性高: 它就是一个编译器+一个库,可以让开发者最大限度的发挥,实现形形色色的AOP程序!
首先需要在build.gradle添加配置
下面是我的配置
import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
minSdkVersion 14
targetSdkVersion 19
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.3'
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
android.libraryVariants.all { variant ->
LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", plugin.project.android.bootClasspath.join(
File.pathSeparator)]
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler)
def log = project.logger
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
compile 'com.android.support:support-v4:23.2.0'
compile 'org.aspectj:aspectjrt:1.8.9'
}
日志类
package com.hao.aoplibrary;
/** * 创建一个日志信息 * @author ZHANGHAOHAO * @date 2017/3/29 */
public class BuildMessage {
public static String buildLogMessage(String clazzSimpleName, String methodName, double methodDuration) {
StringBuilder message = new StringBuilder();
message.append(clazzSimpleName);
message.append(":");
message.append(methodName);
message.append(" --> ");
message.append("[");
message.append(methodDuration);
message.append("ms");
message.append("] \n");
return message.toString();
}
}
监控类
package com.hao.aoplibrary;
import java.util.concurrent.TimeUnit;
public class StopWatch {
private long startTime;
private long endTime;
private long elapsedTime;
public StopWatch() {
}
private void reset() {
startTime = 0;
endTime = 0;
elapsedTime = 0;
}
public void start() {
reset();
startTime = System.nanoTime();
}
public void stop() {
if (startTime != 0) {
endTime = System.nanoTime();
elapsedTime = endTime - startTime;
} else {
reset();
}
}
public long getTotalTimeMillis() {
return (elapsedTime != 0) ? TimeUnit.NANOSECONDS.toMillis(endTime - startTime) : 0;
}
}
一、实现一个自定义注解,截获注解做操作
首先定义一个注解类
package com.hao.aoplibrary.anno;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** * 自定义注解 * * @author ZHANGHAOHAO089 * @date 2017/3/29 */
@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
}
AspectJ实现截获类
package com.hao.aoplibrary;
import android.util.Log;
import com.hao.aoplibrary.anno.MyAnnotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
/** * 自定义注解实例 * * @author ZHANGHAOHAO089 * @date 2017/3/29 */
@Aspect
public class AspectAnno {
@Around("execution(!synthetic * *(..)) && onLogMethod()")
public Object doLogMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
return logMethod(joinPoint);
}
@Pointcut("@within(com.hao.aoplibrary.anno.MyAnnotation)||@annotation(com.hao.aoplibrary.anno.MyAnnotation)")
public void onLogMethod() {
}
private Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
if (myAnnotation!=null) {
String name = myAnnotation.name();
Log.i("ZH-Anno", "name--->" + name);
//TODO 根据传进来的值做操作
}
Object result = joinPoint.proceed();
return result;
}
}
二、截获原方法,并替换
package com.hao.aoplibrary;
import android.util.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import static com.hao.aoplibrary.BuildMessage.buildLogMessage;
/** * 截获某包下的的所有非static方法(static方法另外加一个static修饰的execution或者call即可:execution( static * *..Activity+.*(..)) * * @author ZHANGHAOHA * @date 2017/3/29 */
@Aspect
public class AspectAround {
private static final String POINTCUT_METHOD = "execution(* com.hao.myaoptest.*.*(..)) && target(Object) && this(Object)";
@Pointcut(POINTCUT_METHOD)
public void methodAnnotated() {}
/** * 截获原方法,并替换 */
@Around("methodAnnotated()")
public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
//初始化计时器
StopWatch stopWatch = new StopWatch();
//开始监听
stopWatch.start();
//调用原方法的执行。
Object result = joinPoint.proceed();
//监听结束
stopWatch.stop();
String classType = joinPoint.getTarget().getClass().getName();
Class> clazz = Class.forName(classType);
String clazzSimpleName = clazz.getSimpleName();
String methodName = joinPoint.getSignature().getName();
String msg = buildLogMessage(clazzSimpleName, methodName, stopWatch.getTotalTimeMillis());
Log.i("ZH-AROUND", msg);
return result;
}
}
三、截获方法,在方法之前和之后做一些操作
package com.hao.aoplibrary;
import android.util.Log;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import static com.hao.aoplibrary.BuildMessage.buildLogMessage;
/** * call实例 * * @author ZHANGHAOHA * @date 2017/3/29 */
@Aspect
public class AspectCall {
StopWatch stopWatch;
private static final String POINTCUT_METHOD = "call(* com.hao.myaoptest.MainActivity.initView(..)) && target(Object) && this(Object)";
@Pointcut(POINTCUT_METHOD)
public void methodCall() {}
/** * 在执行方法之前执行 */
@Before("methodCall()")
public void beforeCall(JoinPoint joinPoint) throws Throwable {
// //初始化计时器
stopWatch = new StopWatch();
// //开始监听
stopWatch.start();
String classType = joinPoint.getTarget().getClass().getName();
Class> clazz = Class.forName(classType);
String clazzSimpleName = clazz.getSimpleName();
String methodName = joinPoint.getSignature().getName();
Log.i("ZH-before-CALL", clazzSimpleName + ":" + methodName);
}
/** * 在执行方法之后执行 */
@After("methodCall()")
public void afterCall(JoinPoint joinPoint) throws Throwable {
//监听结束
stopWatch.stop();
String classType = joinPoint.getTarget().getClass().getName();
Class> clazz = Class.forName(classType);
String clazzSimpleName = clazz.getSimpleName();
String methodName = joinPoint.getSignature().getName();
String msg = buildLogMessage(clazzSimpleName, methodName, stopWatch.getTotalTimeMillis());
Log.i("ZH-after-CALL", msg);
}
}
追究其原理
在编译期对目标对象、方法做标记,对目标类、方法进行重构,将PointCut插入目标中,截获该目标的信息以及上下文环境,以达到非侵入代码监控的目的——注意,它只能获得对象的声明,如果对象的声明式接口,那么默认情况下(不使用this、target约束切点),获取的是声明类型,而不是具体运行时的类。
1、编写Aspect:声明Aspect、PointCut和Advise。
2、ajc编织: AspectJ编译器在编译期间对所切点所在的目标类进行了重构,在编译层将AspectJ程序与目标程序进行双向关联,生成新的目标字节码,即将AspectJ的切点和其余辅助的信息类段插入目标方法和目标类中,同时也传回了目标类以及其实例引用。这样便能够在AspectJ程序里对目标程序进行监听甚至操控。
3、execution: 顾名思义,它截获的是方法真正执行的代码区,Around方法块就是专门为它存在的。调用Around可以控制原方法的执行与否,可以选择执行也可以选择替换。
4、call: 同样,从名字可以看出,call截获的是方法的调用区,它并不截获代码真正的执行区域,它截获的是方法调用之前与调用之后(与before、after配合使用),在调用方法的前后插入JoinPoint和before、after通知。它截获的信息并没有execution那么多,它无法控制原来方法的执行与否,只是在方法调用前后插入切点,因此它比较适合做一些轻量的监控(方法调用耗时,方法的返回值等)。
5 、Around替代原理:目标方法体被Around方法替换,原方法重新生成,名为XXX_aroundBody(),如果要调用原方法需要在AspectJ程序的Around方法体内调用joinPoint.proceed()还原方法执行,是这样达到替换原方法的目的。达到这个目的需要双方互相引用,桥梁便是Aspect类,目标程序插入了Aspect类所在的包获取引用。AspectJ通过在目标类里面加入Closure(闭包)类,该类构造函数包含了目标类实例、目标方法参数、JoinPoint对象等信息,同时该类作为切点原方法的执行代理,该闭包通过Aspect类调用Around方法传入Aspect程序。这样便达到了关联的目的,便可以在Aspect程序中监控和修改目标程序。
6 、 Before与After: Before与After只是在方法被调用前和调用之后添加JoinPoint和通知方法(直接插入原程序方法体中),调用AspectJ程序定义的Advise方法,它并不替代原方法,是在方法call之前和之后做一个插入操作。After分为returnning和throwing两类,前者是在正常returning之后调用,后者是在throwing发生之后调用。默认的After是在finally处调用,因此它包含了前面的两种情况。
7 、重要关键字 : 在其它关键字中,必须要注意的就是this、target的使用和区别,同时还有一个很重要的方法Signature.getDeclaringType() AspectJ是在编译期截获的对象信息,因此它获得的标签只是对象的声明(比如:接口、抽象类),而不是运行时具体的对象。如果想要获得运行时对象,就需要用this、target关键字
学习之初学习的blog
http://blog.csdn.net/innost/article/details/49387395
http://blog.csdn.net/crazy__chen/article/details/52014672
http://blog.csdn.net/woshimalingyi/article/details/51476559
https://yq.aliyun.com/articles/58739