likes
comments
collection
share

AspectJ实现切面编程(实战篇)

作者站长头像
站长
· 阅读数 47

前言

在开发过程中,我们可能会遇到需要对同一事件做统一逻辑处理的情况,比如常见的在多个模块需要判断用户是否登录。如果是常规写法就会出现很多冗余代码并且不易维护,这种情况就可以考虑使用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、在appbuild.gradle中添加AspectJ库依赖

dependencies {
    implementation 'org.aspectj:aspectjrt:1.8.13'
}

3、在appbuild.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源码

github.com/VincentStor…

总结

本篇主要介绍了使用AspectJ动态权限申请的具体实现方式,用户登录行为拦截的实现代码这里不做额外的讲解,感兴趣的伙伴可以下载源码进行阅读。本项目使用了AspectJ常用的一些语法,如若不能满足项目的需要,可查阅相关资料学习其他语法的使用。

转载自:https://juejin.cn/post/7366446446818492455
评论
请登录