AOP是Aspect Oriented Programming的缩写,即『面向切面编程』。它和我们平时接触到的OOP都是编程的不同思想,OOP,即『面向对象编程』,它提倡的是将功能模块化,对象化,而AOP的思想,则不太一样,它提倡的是针对同一类问题的统一处理,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
void methodCall(){
long start = System.currentTimeMillis();
Logger.d(TAG, "onAttachContentView, start = "+start);
//doSomething
Logger.d(TAG, "onAttachContentView, end coast = "+(System.currentTimeMillis()-start));
}
AOP做法:
@TimeLog
void methodCall(){
//doSomething
}
只需要在methodCall()
上面添加@TimeLog注解就可以打印方法调用耗时,当然你也可以在解析注解时加入方法名称、方法参数等的打印。
AspectJ实际上是对AOP编程思想的一个实践,AOP虽然是一种思想,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP。目前用得比较火的就是AspectJ了,它是一种几乎和Java完全一样的语言,而且完全兼容Java(AspectJ应该就是一种扩展Java,但它不是像Groovy那样的拓展。)。当然,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好。所以,使用AspectJ有两种方法:
Project根目录下的build.gradle
添加aspectj
插件依赖
buildscript {
......
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.14'
classpath 'org.aspectj:aspectjweaver:1.8.14'
}
.......
}
app/build.gradle
引入aspectj插件
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
apply plugin: 'com.android.application'
android {
defaultConfig {
......
......
}
buildTypes {
......
......
}
final def log = project.logger
final def variants = applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
//方法调用性能,需在debug版本下,注解的方法不能混淆,否则无法成功,所以在跟踪新能问题时需要将混淆关掉
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return
}
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", 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;
}
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'org.aspectj:aspectjrt:1.8.14'
}
再考虑登录判定场景
通常做法:
void doSomeThing(){
// 判断当前用户是否登录
if(LoginHelper.isLogin(this)) {
// doSomeThing, 如果已登录,直接处理
}else{
//doLogin, 跳转到登录页面,先登录
}
}
采用AOP的做法:
@CheckLogin
public void doSomeThing() {
//DO SOMETHING
}
那么CheckLogin
注解如何处理呢?
用来处理触发切面的回调
@Aspect
public class CheckLoginAspectJ {
private static final String TAG = "CheckLogin";
/**
* 找到处理的切点
* * *(..) 可以处理CheckLogin这个类所有的方法
*/
@Pointcut("execution(@com.calvin.aop.login.CheckLogin * *(..))")
public void executionCheckLogin() {
}
/**
* 处理切面
*
* @param joinPoint
* @return
*/
@Around("executionCheckLogin()")
public Object checkLogin(ProceedingJoinPoint joinPoint) throws Throwable {
Log.i(TAG, "checkLogin: ");
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
CheckLogin checkLogin = signature.getMethod().getAnnotation(CheckLogin.class);
if (checkLogin != null) {
Context context = (Context) joinPoint.getThis();
if (MyApplication.isLogin) {
Log.i(TAG, "checkLogin: 登录成功 ");
return joinPoint.proceed();
} else {
Log.i(TAG, "checkLogin: 请登录");
Toast.makeText(context, "请登录", Toast.LENGTH_SHORT).show();
return null;
}
}
return joinPoint.proceed();
}
}
这里要使用Aspect的编译器编译必须给类打上标注,@Aspect
。还有这里的Pointcut
注解,就是切点,即触发该类的条件。
在Pointcut
这里,我使用了execution
,也就是以方法执行时为切点,触发Aspect
类。而execution
里面的字符串是触发条件,也是具体的切点。我来解释一下参数的构成。execution(@com.calvin.aop.login.CheckLogin (..))
这个条件是所有加了CheckLogin
注解的方法或属性都会是切点,范围比较广。
Pointcut
内还可以设定下面这些参数:
切入点类别 | 切入点语法 |
---|---|
Method execution | execution(MethodSignature) |
Method call | call(MethodSignature) |
Constructor execution | execution(ConstructorSignature) |
Constructor call | call(ConstructorSignature) |
Class initialization | staticinitialization(TypeSignature) |
Field read access | get(FieldSignature) |
Field write access | set(FieldSignature) |
Exception handler execution | handler(TypeSignature) |
Object initialization | initialization(ConstructorSignature) |
Object pre-initialization | preinitialization(ConstructorSignature) |
Advice execution | adviceexecution() |
**:表示是任意包名
…:表示任意类型任意多个参数
com.calvin.aop.login.CheckLogin
这是我的项目包名下需要指定类的绝对路径。再来看看@Around
, Around
是指JPoint
执行前或执行后被触发,除了Around
还有其他几种方式。
类型 | 描述 |
---|---|
Before | 前置通知, 在目标执行之前执行通知 |
After | 后置通知, 目标执行后执行通知 |
Around | 环绕通知, 在目标执行中执行通知, 控制目标执行时机 |
AfterReturning | 后置返回通知, 目标返回时执行通知 |
AfterThrowing | 异常通知, 目标抛出异常时执行通知 |
如果使用的是以方法相关为切点,那么使用MethodSignature
来接收joinPoint
的Signature
。如果是属性或其他的,那么可以使用Signature
类来接收。之后可以使用Signature
来获取注解类。,那么通过jointPoint.getThis()
获取使用该注解的的上下文对象。
创建完Aspect类之后,还需要一个注解类,它的作用是:哪里需要做切点,那么哪里就用注解标注一下,这样方便快捷。
package com.calvin.aop.login;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) //可以注解在方法 上
@Retention(RetentionPolicy.RUNTIME) //运行时(执行时)存在
public @interface CheckLogin {
}
参考文献
安卓架构师必备之Android AOP面向切面编程详解
AOP实现的工具类AopArms