AOP,即面向切面编程,是对OOP的补充。在2016年我第一次看到AOP,但并未深入去学习,只是默默的记在自己的技术堆栈txt上,归类到“高级技术”(自我提升第三阶段)那一块。然而在最近2个月学习设计模式的过程中,在《设计模式之禅完整版 秦晓波》中的“代理模式”中又看到了,并初步了解AOP的实现原理。就在上周看网上公开课的视频时发现有一个“AOP面向切面设计编程”,接着后续的1个半小时就在看视频中度过,看完收获满满。
经过记录、整理后与大家分享下(这里IDE为AS)。
前往官网:http://www.eclipse.org/aspectj/downloads.php下载最新的jar包,然后解压出aspectj.jar文件;
这里有必要提一下,使用AOP时,我们是用aspectj替换javac进行java文件的编译,因此在gradle的配置上会比较麻烦点。
后面给的是官网的配置地址: http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/
但是这个已经太久没更新了,而且我试过了,不能用了。下面来看看我查到的可以用的配置:
在module的gradle文件中
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
//上面的需要手动添加,不会提示进行导包的
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
apply plugin: 'com.android.application'
//这里不需要官方的那个plugin
repositories {
mavenCentral()
}
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "com.example.aopproject"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
compile files('libs/aspectjrt.jar')
}
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",//这个是jdk的版本
"-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);
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:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
这里需要一些注解的知识,小伙伴们不熟的可能需要去补一补:)
代码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//运行时可以通过得到,如果Class则无法再Around里面获取相关的信息
public @interface BehaviorAnn {
String value();
int level();
}
这里需要注意的是,如果在@Retention中使用RetentionPolicy.CLASS的话,那么后续如果你想获取自定义注解是不可行的,这个我是被视频坑了,不知道为什么视频里写CLASS居然也能获取==!。
既然有自定义注解,那必然也会有对应的解释器了,而且核心的操作基本都在这进行处理了。
下面让我们一步一步来写解释器。
给解释器类需要添加@Aspect注解。
@Aspect
public class BehaviorAspect {
...
}
获取切点。
private static final String POINTCUT_METHOD = "execution(@com.example.aopproject.aop.BehaviorAnn * *(..))";
//获取切点
@Pointcut(POINTCUT_METHOD)
public void annBehavior(){
//空的
}
这里对POINTCUT_METHOD立面的进行下说明。
用过正则表达式的可能会熟悉点,那2个其实就是个通配符,这2个号的前面至少需要一个空格。具体如下:
com.example.aopproject.aop.BehaviorAnn: 要处理的注解
第一个*号:代表所有的类
第二个*号:代表所有方法
(..):代表匹配所有的形参
下面举例说明:
execution(@com.example.aopproject.BehaviorAnn * sayGoodbye(..)):匹配sayGoodbye方法,参数不限制--方法前面有个空格,官方是用一个.来代替空格
execution(@com.example.aopproject.BehaviorAnn * *(int)):匹配形参只有一个int
execution(@com.example.aopproject.BehaviorAnn * *(String,int)):匹配形参第一个为String,第二个为int的方法
@AspectJ 注解提供六种通知Advice类型:
@Before:前置通知
@Around:环绕通知
@AfterThrowing:异常抛出通知
@@AfterReturning:后置通知
@After:最终通知,相当于代码 finally(之前没有的)
所谓的前置通知其实也就是在调用方法之前先调用该方法,后置同理。而环绕则可以自定义先后顺序,需要调用原来的方法的时候就调用point.proceed()即可。
上代码:
@Around("annBehavior()")//括号里的就是前面空实现的annBehavior(),当然直接用POINTCUT_METHOD也是可以的
public Object dealPoint(ProceedingJoinPoint point)
throws Throwable {
// 方法执行前
MethodSignature methodSignature = (MethodSignature) point.getSignature();// 获取方法签名
BehaviorAnn behaviorTrace = methodSignature.getMethod().getAnnotation(BehaviorAnn.class);// 获取注解(不是RUNTIME获取不到的)
int level = behaviorTrace.level();// 调用注解的方法
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
// 方法执行时
final TimeWatcher stopWatch = new TimeWatcher();
stopWatch.start();
Object object = null;
try {
object = point.proceed();
} catch (Exception e) {
e.printStackTrace();
}
stopWatch.stop();
// 方法执行完成
DebugLog.log(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));
return object;
}
需要注意的一下地方也在代码中指明了。
下面看下@AfterThrowing和@AfterReturning的用法:
@AfterThrowing(value = POINTCUT_START_METHOD, throwing = "ex")
public void AfterThrowing(Throwable ex) {//和括号中的保持一致
Log.d(TAG, "AfterThrowing:" + ex.getMessage());
}
@AfterReturning(value = POINTCUT_START_METHOD, returning = "result")
public void AfterReturning(JoinPoint point, Object result) {//和括号中的保持一致
Log.d(TAG, ((String) result));
Log.d(TAG, "AfterReturning");
}
最后就是给我们需要检测的方法添加上自定义的注解。
@BehaviorAnn("问候")
private void greet() {
SystemClock.sleep(3000L);
Log.d(TAG, "How are U?");
}
1.按我个人理解,整个AOP的过程简单来说就是遇到有自定义注解的会将其作为切点,直接切入到Aspect中的进行通知处理,而通知处理中的point.proceed()其实就是回到了自身方法的实现。
2. 方法中传入如果传入JoinPoint参数就可以获得拦截的切点信息。
3. 关于这几个处理的最好是根据需要选择其中的部分或者全部方法,按照下面的顺序编写代码Before—Around—AfterThrowing—AfterReturning—After。小伙伴可以尝试换下顺序,打印日志进行观察结果。
4. AOP主要的使用场景:①日志记录②权限管理③事务管理④性能监测⑤并发⑥错误处理。自后会给个本文性能监测(打印方法运行耗时)的demo,至于其他几个场景的应用(尤其是权限管理),希望有demo的小伙伴可以分享下。
5. 如果想了解点Spring中的aop,可以前往http://blog.csdn.net/javaliuzhiyue/article/details/9357849。
6. 本文的小demo下载地址:AOP例子