AspectJ实现切面编程(实战篇)
前言
在开发过程中,我们可能会遇到需要对同一事件做统一逻辑处理的情况,比如常见的在多个模块需要判断用户是否登录。如果是常规写法就会出现很多冗余代码并且不易维护,这种情况就可以考虑使用
AOP
切面编程,将使代码更加解耦并易维护,下面就来介绍一下实现切面思想的具体方案AspectJ
框架。
AspectJ能做什么?
AOP
都是为一些相对基础且固定的需求服务,实际常见的场景大致包括:统计埋点、日志打印、行为拦截、性能监控、动态权限申请等,本项目编码实现其中两个场景,行为拦截和动态权限申请,具体功能就是用户登录行为拦截
和通过注解方式去动态获取权限以及抓取结果
。
配置AspectJ
1、在project
根目录的build.gradle
中添加AspectJ
插件
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
2、在app
的build.gradle
中添加AspectJ
库依赖
dependencies {
implementation 'org.aspectj:aspectjrt:1.8.13'
}
3、在app
的build.gradle
中根目录下添加如下配置
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
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;
}
}
}
}
配置这么复杂?这个我找了相关资料,还没有看到更简洁的配置方式,因为AspectJ
是对java
的扩展,而且是完全兼容java
的。但是编译时得用AspectJ
专门的编译器,这里的配置就是使用AspectJ
的编译器,仅仅添加AspectJ
依赖是不行的。
具体实现
1、创建注解类
// 权限申请的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//代表运行时生效
public @interface Permission {
//权限申请内容的集合
String[] value();
//权限申请的code
int requestCode() default MyPermissionActivity.PARAM_REQUEST_CODE_DEFAULT;
}
2、创建切面类
@Aspect①
public class PermissionAspect {
@Pointcut("execution(@com.example.aspect.annotation.Permission * *(..)) && @annotation(permission)")②
public void pointActionMethod(Permission permission) {}
// 对方法环绕监听,pointActionMethod()是指上面定义的方法名,permission是指上面参数名,要一一对应
@Around("pointActionMethod(permission)")③
public void aProceedingJoinPoint(final ProceedingJoinPoint point, Permission permission) throws Throwable {
Context context = null;
final Object thisObject = point.getThis();
if (thisObject instanceof Context) {
context = (Context) thisObject;
} else if (thisObject instanceof Fragment) {
context = ((Fragment) thisObject).getActivity();
}
if (null == context || permission == null) {
throw new IllegalAccessException("null == context || permission == null is null");
}
// 执行获取权限的逻辑
requestPermission(context, permission, point,thisObject);
}
①处@Aspect
标识此类交给Aspect
的编译器编译
②处@Pointcut
代表切入点,需要配置切入点参数,使用execution
表示以方法执行时为切点,com.example.aspect.annotation.Permission
为切入点注解类的绝对路径,根据自己项目需要修改。
**
:表示是任意包名..
:表示任意类型任意多个参数
③处@Around
代表环绕方法插入代码,可在方法执行前或执行后插入代码。
3、使用注解获取权限
//点击事件去获取权限
public void permissionRequestTest(View view) {
testRequest();
}
@Permission(value = Manifest.permission.READ_EXTERNAL_STORAGE, requestCode = 200)
public void testRequest() {
Toast.makeText(this, "权限申请成功...", Toast.LENGTH_SHORT).show();
}
@PermissionCancel
public void testCancel() {
Toast.makeText(this, "权限被拒绝", Toast.LENGTH_SHORT).show();
}
@PermissionDenied
public void testDenied() {
Toast.makeText(this, "权限被拒绝(用户勾选了,不再提醒)", Toast.LENGTH_SHORT).show();
}
通过上面代码,可以看到使用这种方式去获取权限,代码非常简洁,也有很好的解耦性。
github源码
总结
本篇主要介绍了使用
AspectJ
动态权限申请的具体实现方式,用户登录行为拦截的实现代码这里不做额外的讲解,感兴趣的伙伴可以下载源码进行阅读。本项目使用了AspectJ
常用的一些语法,如若不能满足项目的需要,可查阅相关资料学习其他语法的使用。
转载自:https://juejin.cn/post/7366446446818492455