AOP面向切面编程近几年挺火的,用处大概就是为了让多个模块拥有同一个功能(比如执行同一个方法),如果用面向对象,就相当麻烦,但如果用AOP(比如用AspectJ),利用注解就可以实现,下面我演示一下使用步骤。
准备工作:
1、下载jar包 aspectj-1.8.5.jar,下载后打开它,安装,下一步到底就中。
2、新开一个安卓项目,配置gradle。我把我的贴出来,大伙儿直接CV就中。
apply plugin: 'com.android.application'
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'
}
}
repositories {
mavenCentral()
}
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",
"-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;
}
}
}
}
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.example.songzeceng.studyofaop"
minSdkVersion 24
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'
}
}
}
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.1.0'
testCompile 'junit:junit:4.12'
compile files('libs/aspectjrt.jar')
}
唯独记得android标签下的compileSdkVersion、applicationId这些要是自己的,莫忘了改
3、而后把aspectjrt.jar放到项目目录下的libs目录中(aspectjrt.jar在用户目录下aspectj1.8/lib中)
这样准备工作就完成了,下面我们正儿八经开始写代码
1、创建一个注解,做为向aspectj传递数据的"intent意图",这个意图里的值,我们定义一个方法value来设置
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
可见,这个注解在方法上、运行时生效。于是,被它标记的方法,我们就可以用来给aspectj进行数据的传输
2、我们在主活动中定义一个方法test(),用来检查网络权限的赋予与否,用上我们的注解
@MyAnnotation(value = Manifest.permission.INTERNET)
private int test() {
Log.i(TAG, "网络权限检查");
return 0;
}
3、然后是我们的核心类MyAspectJ,做为触发切面时的回调。
@Aspect
public class MyAspectJ {
private static final String TAG = "MyAspectJ";
@Pointcut("execution(@com.example.songzeceng.studyofaop.MyAnnotation * *(..))")
public int executeAj() {
Log.i(TAG, "executeAj()......");
return 1;
}
...
}
@AspectJ注解:给我们的类标上它,当切面被触发时,就会执行这个类里面的一些操作
@Pointcut注解:切点,也就是触发条件。
这里头有参数,我解释一下:
execution():表示执行时触发切面。
@....MyAnnotation:执行这个注解时(也就是遇到@Annotation,就触发)
*:任意包名
*(..):任意返回值,任意参数列表
4、然后介绍仨注解@Before,@Around,@After
这三个注解使用时,都要传入相对的是哪个方法的方法名
@Before("executeAj()")
public void before () {
Log.i(TAG, "before executeAj()....");
}
@Around("executeAj()")
public Object aroundExecuteAj(ProceedingJoinPoint joinPoint) throws Throwable {
Log.i(TAG, "around executeAj()....");
Object o = null;
....
return o;
}
@After("executeAj()")
public void after () {
Log.i(TAG, "after executeAj()....");
}
使用注解的方法的方法名、返回值、参数列表都无所谓。
@Around是替换的意思,也就是说,aroundExecuteAj()使用了@Around("executeAj()"),那么,executeAj()将不会被执行,转而执行aroundExecuteAj()。故而,执行test()的时候,执行的是aroundExecuteAj()
@Before望文生义,是在执行test()之前,被调用的方法。但这里的执行,不是说在活动里直接调用test(),而是调用ProceedingJoinPoint.proceed()之前。
@After则是在切点@Pointcut方法或@Around方法执行完之后执行的
5、给aroundExecuteAj()传入ProceedJoinPoint参数。
方才我们说了@Before和@After的前后,是ProceedJoinPoint.proceed()的前后,我们就通过传参的方式获取ProceedJoinPoint对象,并调用proceed()方法
@Around("executeAj()")
public void aroundExecuteAj(ProceedingJoinPoint joinPoint) throws Throwable {
Log.i(TAG, "around executeAj()....");
Object o = null;
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
MyAnnotation myAnnotation = methodSignature.getMethod().getAnnotation(MyAnnotation.class); // 反射
String permission = myAnnotation.value();
Context context = (Context) joinPoint.getThis();
if (context.checkPermission(permission, android.os.Process.myPid(), android.os.Process.myUid()) == PackageManager.PERMISSION_GRANTED) {
o = joinPoint.proceed();
Log.i(TAG, "权限已被授予....");
} else {
Log.i(TAG, "权限未被授予....");
}
if (o != null) {
Log.i(TAG, "o is " + o.getClass().getCanonicalName());
}
...
}
ProceedingJoinPoint对象里面保存着我们的触发切面的方法test()的一些信息,在处理过程中,我们通过反射获取自定义注解@MyAnnotation里的value()方法返回值(也就是网络权限),以及调用test()的对象(joinPoint.getThis()方法)
而后就是重点了,正儿八经在切面回调里调用test()方法,是joinPoint.proceed()方法,而@Before方法,在这一步之前被调用,@After方法却是在@Around方法执行完以后执行的。
另外,joinPoint.proceed()方法的返回值,就是test()的返回值。
6、打印其他信息
Log.i(TAG, "joinPoint`s kind :" + joinPoint.getKind());
Log.i(TAG, "joinPoint`s signature :" + joinPoint.getSignature().getName());
Log.i(TAG, "joinPoint`s source location :" + joinPoint.getSourceLocation().getFileName());
Log.i(TAG, "joinPoint`s toLongString() :" + joinPoint.toLongString());
kind是@Pointcut的信息(execution则是方法执行时,即method-execution)
signature是test()的方法名
sourceLocation是test()方法在哪个类
toLongString():@Pointcut的详细信息
输出结果:
最后,我贴一下joinPoint.proceed()方法执行和不执行时,@Before,@Around,@After和test的执行情况
执行joinPoint.proceed():
不执行joinPoint.proceed()方法:
好了,最基础aspectJ就是这样了。可以看到,用上了@MyAnnotation注解的方法都会触发切面,给开发带来的方便确实不是一点半点。