美文网首页
AOP面向切面编程

AOP面向切面编程

作者: Spring618 | 来源:发表于2018-11-02 20:51 被阅读7次

定义:在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

引出面向切面编程

场景:APP的网络处理,没网是点击是没反应的,有网的时候点击才能跳转。

方案:

  1. 每个方法做判断 if(有网) 跳转 这种方法太死板,灵活性差,不便于统一管理,如后期需求更改则改动量大;
  2. IOC 注解
  3. 面向切面编程AOP

需求:


AOP.png

解释:点击账户、转账、理财这三个按钮都需要判断网络状态,如果有网络则跳转到相应页面,否则弹出Toast提示,设置按钮不需要检查,直接跳转进设置页面。

代码1

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // 账户 需要网络监测
    public void request(View view) {
        if (Utils.isNetworkConnected(this)) {
            Intent intent = new Intent(this, AccountActivity.class);
            startActivity(intent);
        } else {
            //提示网络不可用,可能是Toast或者Dialog
            Toast.makeText(this, "网络不可用", Toast.LENGTH_LONG).show();
        }
    }

    // 转账 需要网络监测
    public void request1(View view) {
        if (Utils.isNetworkConnected(this)) {
            Intent intent = new Intent(this, TransActivity.class);
            startActivity(intent);
        } else {
            //提示网络不可用,可能是Toast或者Dialog
            Toast.makeText(this, "网络不可用", Toast.LENGTH_LONG).show();
        }
    }

    // 理财 需要网络监测
    public void request2(View view) {
        if (Utils.isNetworkConnected(this)) {
            Intent intent = new Intent(this, FinaceActivity.class);
            startActivity(intent);
        } else {
            //提示网络不可用,可能是Toast或者Dialog
            Toast.makeText(this, "网络不可用", Toast.LENGTH_LONG).show();
        }
    }

    // 设置 不需要网络监测
    public void request3(View view) {
        Intent intent = new Intent(this, SettingsActivity.class);
        startActivity(intent);
    }
}

Utils:

public class Utils {
    public static boolean isNetworkConnected(Context context) {
        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
            if (mNetworkInfo != null) {
                Log.e("net", "网络可用");
                return mNetworkInfo.isAvailable();
            }
        }
        Log.e("net", "网络不用");
        return false;
    }
}

四个跳转页面比较简单,就不贴出来了,就是一个布局里见加一个TextView。

我们可以看出,如果是这样编写代码的话,有诸多问题。

  1. 重复代码,很多点击事件都需要进行网络状态的检查,但是这不是我们的核心业务;
  2. 没有讲这种检查机制统一管理,后面如果需求修改,如不弹Toast,改用Dialog提示,这样工作量很大。

接下来就引出我们AOP编程了。那么什么是AOP、AOP有什么好处?

面向切面(AOP)其实就是把众多方法中的所有共有代码全部抽取出来,放置到某个地方集中管理,然后在具体运行时,再由容器动态织入这些共有代码的话,最起码可以解决两个问题:
1.1 Android程序员在编写具体的业务逻辑处理方法时,只需关心核心的业务逻辑处理,既提高了工作效率,又使代码变更简洁优雅。
1.2 在日后的维护中由于业务逻辑代码与共有代码分开存放,而且共有代码是集中存放的,因此使维护工作变得简单轻松。

代码2

使用AOP面向切面编程,这里需要基于第三方的一些编译工具 AspectJ

步骤流程:
1 下载 aspectj-1.8.10.jar 文件
2 双击安装 一直点击下一步
3 新建应用 然后在 build.gradle 里面添加配置
4 去安装目录下面 copy 一个开发包
5 写代码

AspectJ的下载地址:http://www.eclipse.org/aspectj/

build.gradle 里面添加配置:

apply plugin: 'com.android.application'
// 拷AspectJ的构建代码BEGIN

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'

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

repositories {
    mavenCentral()
}

// 拷AspectJ的构建代码END
android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.ivyzh.aop"
        minSdkVersion 15
        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'
        }
    }
}

// 拷AspectJ的构建代码BEGIN
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的构建代码END


dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation files('libs/aspectjrt.jar')
    //  compile 'org.aspectj:aspectjrt:1.8.9' 或者使用这种方式
}

去安装目录下面 copy一个开发包(D:\develop\aspectj1.9\lib\aspectjrt.jar)放在libs目录下面,或者使用gradle直接引入,不需要拷贝这个。

compile 'org.aspectj:aspectjrt:1.8.9'

开发前的准备已经完成,接下来我们开始编写文章。

先看MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // 账户 需要网络监测
    @CheckNet(R.string.str_net_error)
    public void request(View view) {
        Intent intent = new Intent(this, AccountActivity.class);
        startActivity(intent);
    }

    // 转账 需要网络监测
    @CheckNet(R.string.str_net_error1)
    public void request1(View view) {
        Intent intent = new Intent(this, TransActivity.class);
        startActivity(intent);
    }

    // 理财 需要网络监测
    @CheckNet(R.string.str_net_error2)
    public void request2(View view) {
        Intent intent = new Intent(this, FinaceActivity.class);
        startActivity(intent);
    }

    // 设置 不需要网络监测
    public void request3(View view) {
        Intent intent = new Intent(this, SettingsActivity.class);
        startActivity(intent);
    }
}

简洁很多了,专注写核心逻辑即可。

CheckNet注解:

/**
 * 标记切点 注解
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNet {
    int value();
}

SectionAspect 切点处理:

/**
 * 处理切点
 */

@Aspect
public class SectionAspect {


    /**
     * 找到处理的切点
     * * *(..)  可以处理所有的方法
     */
    @Pointcut("execution(@com.ivyzh.aop.simple2.activities.CheckNet * *(..))")
    public void checkNetBehavior() {

    }

    /**
     * 处理切面
     */
    @Around("checkNetBehavior()")
    public Object checkNet(ProceedingJoinPoint joinPoint) throws Throwable {
        // Log.d("TAG", "checkNet 处理切面....");// 此处Log日志不可见 MMP
        //Toast.makeText(getContext(joinPoint.getThis()), "此处日志不可见", Toast.LENGTH_LONG).show();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        CheckNet checkNet = signature.getMethod().getAnnotation(CheckNet.class);
        if (checkNet != null) {//获取CheckNet注解
            Object obj = joinPoint.getThis();
            Context context = getContext(obj);
            if (context != null) {
                if (!isNetworkConnected(context)) {
                    int resId = checkNet.value();
                    String msg = context.getString(resId);
                    //Toast.makeText(context, msg + " 此处打印日志不可见", Toast.LENGTH_LONG).show();
                    showDialog(context);
                    return null;
                }
            }
        }

        return joinPoint.proceed();
    }

    private void showDialog(Context context) {
        new AlertDialog.Builder(context)
                .setTitle("网络不可用")
                .setMessage("请检查你的网络哦")
                .setPositiveButton("去设置", null)
                .show();
    }


    @SuppressLint("NewApi")
    private Context getContext(Object object) {
        if (object instanceof Activity) {
            return (Activity) object;
        } else if (object instanceof Fragment) {
            Fragment fragment = (Fragment) object;
            return fragment.getContext();
        } else if (object instanceof View) {
            View view = (View) object;
            return view.getContext();
        }
        return null;
    }

    private boolean isNetworkConnected(Context context) {
        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
            if (mNetworkInfo != null) {
                return mNetworkInfo.isAvailable();
            }
        }
        return false;
    }
}

注意以上每一个步骤和细节,以防止踩坑。

思考:这样会不会影响到性能呢?
不会。
运行按钮 我们用了 aspectj 第三方的编译器 ,class 文件是由 aspect 去编译,是因为 aspect 会去拷贝代码的
class 文件 代码其实是如下:


反编译AOP.png

其实是代码已经改了我们class文件。

总结:

AspectJ的使用核心就是它的编译器,它就做了一件事,将AspectJ的代码在编译期插入目标程序当中,运行时跟在其它地方没什么两样,因此要使用它最关键的就是使用它的编译器去编译代码ajc。ajc会构建目标程序与AspectJ代码的联系,在编译期将AspectJ代码插入被切出的PointCut中,已达到AOP的目的。
因此,无论在什么IDE上(如果使用命令行就可以直接使用ajc编译了),问题就是让IDE使用ajc作为编译器编译代码。

AOP的应用场景:网络判断、权限判断、埋点...

END.

相关文章

网友评论

      本文标题:AOP面向切面编程

      本文链接:https://www.haomeiwen.com/subject/rsgcxqtx.html