Android面向切面编程(AOP)之使用动态代理和预编译(Aspectj)方式实现

  • AOP(Aspect-oriented programming)是面向切面的编程,可以通过预编译方式和运行期通过动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一项技术。
  • 场景

Android面向切面编程(AOP)之使用动态代理和预编译(Aspectj)方式实现_第1张图片

当用户未登录时,如果是操作业务模块A或者其它业务模块,都需要先登录,这时候登录模块就是针对三个业务模块的面向切面的模块,这个就是AOP的使用场景。以往是在每个业务模块都添加是否登录的判断,但是现在通过AOP就不需要每个地方都添加是否登录的判断,通过使用动态代理或者预编译方式实现,在方法的前面增加判断

动态代理

     用户数据在删除前都要进行备份

  • 定义接口
public interface DBoperation {
    void delete();
    void insert();
    void save();
}
  • 定义动态代理
import android.util.Log;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DBhandler implements InvocationHandler {

   private DBoperation db;
    private static final String TAG="DB invoke==";

    public DBhandler(DBoperation db) {
        this.db = db;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      
        if(db!=null){
            Log.e(TAG,"备份数据开始");
            db.save();
            Log.e(TAG,"备份数据结束");
            return method.invoke(db,args);
        }
        return null;

    }
}
  • 定义实例
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import androidx.annotation.Nullable;
import java.lang.reflect.Proxy;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class AopActivity extends Activity implements DBoperation {

    private static final String TAG = "DB invoke==";

    @BindView(R.id.btn_click)
    Button btnClick;
    private DBoperation instance;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_btn);
        ButterKnife.bind(this);
        instance = (DBoperation) Proxy.newProxyInstance(DBoperation.class.getClassLoader()
                , new Class[]{DBoperation.class}, new DBhandler(this));
    }


    @OnClick(R.id.btn_click)
    public void onViewClicked() {
        instance.delete();
    }

    @Override
    public void delete() {
        Log.e(TAG, "数据删除");
    }

    @Override
    public void insert() {
        Log.e(TAG, "数据插入");
    }

    @Override
    public void save() {
        Log.e(TAG, "数据备份");
    }
}

点击删除代码,日志如下

2020-04-18 22:20:58.952 9361-9361/com.xiaoma.brick E/DB invoke==: 备份数据开始
2020-04-18 22:20:58.952 9361-9361/com.xiaoma.brick E/DB invoke==: 数据备份
2020-04-18 22:20:58.952 9361-9361/com.xiaoma.brick E/DB invoke==: 备份数据结束
2020-04-18 22:20:58.952 9361-9361/com.xiaoma.brick E/DB invoke==: 数据删除

总结:在做删除操作时,可以在对应的操作前面进行数据备份,但是需要不同的业务实现同一个接口,不同实现实现不同的删除和备份操作,但是通过动态代理都可以保证在做删除操作前进行保存.

预编译Aspectj

在项目的build.gradle内添加

 dependencies {
       。....

        classpath 'org.aspectj:aspectjtools:1.8.9'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }

在 app目录下的bulid.gradle 文件中配置如下信息:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.9'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}
android {
  
}

dependencies {
   ......

    implementation 'org.aspectj:aspectjrt:1.8.13'

}

// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
// 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
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
            }
        }
    }
}

上面配置号后,新建注解类用于获取行为统计

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 ClickBehavior {
    String value();
}

新建注解类用于统一处理是否登录

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 LoginCheck {
}

新建需要自定义行为的类--行为统计的前面处理

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

@Aspect
public class ClickBehaviorAspect {

    private static final String TAG = "aspectj >>";

    @Pointcut("execution(@com.xiaoma.pattern.aop.ClickBehavior * *(..))")
    public void methodPointCut() {
    }

    @Around("methodPointCut()")
    public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取签名方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //获取方法所属的类名
        String className = methodSignature.getDeclaringType().getSimpleName();
       //获取方法名
        String methodName = methodSignature.getName();
     //获取方法的注解值
        String value = methodSignature.getMethod().getAnnotation(ClickBehavior.class).value();

        long begin = System.currentTimeMillis();
        Log.e(TAG, "ClickBehavior Method Star");
        Object result = joinPoint.proceed();
        long duration = System.currentTimeMillis() - begin;
        Log.e(TAG, "ClickBehavior Method End");
        Log.e(TAG,String.format("统计了:%s功能,在%s类的%s方法,用时%d ms",value,className,methodName,duration));
        return result;
    }
}

需要自定义行为的类--是否需要提前登录处理

import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class LoginBehaviorAspect {

    private static final String TAG = "aspectj >>";

    @Pointcut("execution(@com.xiaoma.pattern.aop.LoginCheck * *(..))")
    public void methodPointCut() {
    }

    @Around("methodPointCut()")
    public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        Context context = (Context) joinPoint.getThis();
        if (false) {  //定义是否登录的参数,默认没有登录
            Log.e(TAG, "已登录");
            return joinPoint.proceed();
        } else {
            context.startActivity(new Intent(context, AopActivity.class));
            return null;
        }
    }
}

之后再需要登录或者行为统计的地方使用

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import androidx.annotation.Nullable;
import com.xiaoma.brick.R;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class AspectjActivity extends Activity {
    private static final String TAG = "aspectj";



    /**
     * AspectJ 面向切面编程的框架
     * as3.0.1  gradle4.4  ndk r17-----
     * as3.2.1  gradle4.6
     * as3.4.0 gradle5.1.1  有问题
     * 

* 一个是面向切面登录,一种是统计 *

* 切入点PointCut * Advice 通知 before after around * Joint Point 连接点 */ @ClickBehavior("area") @LoginCheck private void area() { Log.e(TAG, "area"); } @ClickBehavior("login") private void login() { Log.e(TAG, "login"); } }

login的日志如下

2020-04-18 23:22:03.986 10168-10168/com.xiaoma.brick E/aspectj >>: ClickBehavior Method Star
2020-04-18 23:22:03.986 10168-10168/com.xiaoma.brick E/aspectj: login
2020-04-18 23:22:03.986 10168-10168/com.xiaoma.brick E/aspectj >>: ClickBehavior Method End
2020-04-18 23:22:03.987 10168-10168/com.xiaoma.brick E/aspectj >>: 统计了:login功能,在AspectjActivity类的login方法,用时0 ms

area的日志如下:

点击后直接跳转到登录页面,如果设置已经登录则相关内容如下

    @Around("methodPointCut()")
    public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        Context context = (Context) joinPoint.getThis();
        if (true) {
            Log.e(TAG, "已登录");
            return joinPoint.proceed();
        } else {
            context.startActivity(new Intent(context, AopActivity.class));
            return null;
        }
    }

///日志如下
2020-04-18 23:24:30.680 10600-10600/com.xiaoma.brick E/aspectj >>: 已登录
2020-04-18 23:24:30.682 10600-10600/com.xiaoma.brick E/aspectj >>: ClickBehavior Method Star
2020-04-18 23:24:30.682 10600-10600/com.xiaoma.brick E/aspectj: area
2020-04-18 23:24:30.682 10600-10600/com.xiaoma.brick E/aspectj >>: ClickBehavior Method End
2020-04-18 23:24:30.683 10600-10600/com.xiaoma.brick E/aspectj >>: 统计了:area功能,在AspectjActivity类的area方法,用时0 ms

 

你可能感兴趣的:(架构师,aop)