AOP编程

作者: 优疏 | 来源:发表于2018-04-20 14:46 被阅读1次

    一、基本概念介绍

    AOP全称Aspect Oriented Programming,即面向切面编程。通过预编译方式或者运行期动态代理等实现程序功能统一维护的一种技术,利用AOP可以实现对业务逻辑中不同部分进行分离,从而降低耦合,提高程序的可复用性,主要用于实现日志记录、性能统计、埋点统计、安全控制、异常处理等。

    在没有AOP之前,如果各个模块都要打印日志,就要自己处理,都要认为编写日志代码。若以后日志模块修改API,则使用它们的地方都要改。AOP的目标就是解决上述提到的这种麻烦的问题。AOP的目标就是把这些功能集中起来,放在一个统一的地方来控制和管理。比如我们可以设置Aspect,来管理软件中一些特殊函数调用的权限问题,如检验登录流程等。

    1.术语

    • 横切关注点(Cross-cutting concerns)
      在面对对象编程中,我们希望在不同的模块代码中添加一些类似的代码,如希望在数据访问层中类中添加日志,或者在View的点击处添加点击事件的埋点统计。横切关注点就是那些经常发生的在业务逻辑处理中需要关注的地方,在代码中添加一些相同的附属功能等。

    • 连接点(Joint point)
      程序中可能作为代码注入目标的点,如方法调用或者方法入口。

    • 通知(Advice)
      注入到class文件中的代码。典型的有before(在目标方法执行前的动作)、after(在目标方法执行后的动作)、around(替换目标方法的动作)。


      image.png
    • 切入点(Pointcut)
      确定什么时机触发通知。

    • JoinPoint类型


      image.png
    • 常用通配符


      image.png
    • MethodSignature
      定义MethodSignature的条件表达式与定义一个方法类型,结构如下:

      • 表达式
        [@注解] [访问权限] 返回值的类型 类全路径名(包名+类名).函数名(参数)
      • 例子
        public (..) :表示任意参数任意包下的任意函数名任意返回值的public方法
        @com.example.TestAnnotation com.example..(int) :表示com.example下被TestAnnotation注解了的带一个int类型参数的任意名称任意返回值的方法
    • ConstructorSignature
      ConstructorSignature与MethodSignature类似,只不过构造函数没有返回值,而且函数名必须叫new。

      • 表达式
        [@注解] [访问权限] 类全路径名(包名+类名).new(参数)
      • 例子
        public *..People.new(..) :表示任意包名下面People这个类的public构造器,参数列表任意
    • FieldSignature
      与在类中定一个一个成员变量的格式相类似

    • 表达式
      [@注解] [访问权限] 类型 类全路径名.成员变量名

    • 例子
      String com.example..People.lastName :表示com.example包下面的People这个类中名为lastName的String类型的成员变量

    • FieldSignature
      TypeSignature其实就是用来指定一个类的。因此我们只需要给出一个类的全路径的表达式即可


      image.png

      上面几个是比较常用的选择关键字。
      同时,Pointcut之间可以使用"&&|!"这些逻辑符号进行拼接,将两个Pointcut进行拼接,完成一个最终的对Pointcut的选择操作

    • 切面(Aspect)
      切入点和通知的组合成一个切面,如在应用通过定义一个Pointcut和Advice来添加一个日志切面。
      织入(Weaving)
      注入代码到目标位置的过程。

    2.AOP织入类型

    • 编译时织入
      在Java类文件编译的时候进行织入,如使用AspectJ的织入编译器。
    • 类加载时织入
      通过自定义类加载器ClassLoader的方式在目标类被加载到虚拟机之前进行类的字节代码的增强。
    • 运行时织入
      切面在运行的某个时刻被动态织入,基本原理是使用Java的动态代理技术。

    二、AspectJ实现AOP

    AspectJ功能强大,支持编译期的代码织入,不影响应用的性能,同时提供易于使用的API。

    1.AspectJ基本使用

    • build.gradle配置
      在build.gradle(Project)添加classpath关联
    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    
    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.3.3'
            classpath 'org.aspectj:aspectjtools:1.8.1'
        }
    }
    
    allprojects {
        repositories {
            jcenter()
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    
    • 单独一个module用于编写aop切面代码,在该module的build.gradle中添加如下配置(如果我们的切面代码不是独立为一个module的话可以忽略这一步),可以参考如下配置
    apply plugin: 'com.android.library'
    import com.android.build.gradle.LibraryPlugin
    import org.aspectj.bridge.IMessage
    import org.aspectj.bridge.MessageHandler
    import org.aspectj.tools.ajc.Main
    
    android {
        compileSdkVersion 26
        buildToolsVersion "26.0.3"
    
        defaultConfig {
            minSdkVersion 14
            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'
            }
        }
    }
    
    android.libraryVariants.all { variant ->
        LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
        JavaCompile javaCompile = variant.javaCompile
        javaCompile.doLast {
            String[] args = ["-showWeaveInfo",
                             "-1.5",
                             "-inpath", javaCompile.destinationDir.toString(),
                             "-aspectpath", javaCompile.classpath.asPath,
                             "-d", javaCompile.destinationDir.toString(),
                             "-classpath", javaCompile.classpath.asPath,
                             "-bootclasspath", android.bootClasspath.join(
                    File.pathSeparator)]
    
            MessageHandler handler = new MessageHandler(true);
            new Main().run(args, handler)
    
            def log = project.logger
            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:
                    case IMessage.INFO:
                        log.info message.message, message.thrown
                        break;
                    case IMessage.DEBUG:
                        log.debug message.message, message.thrown
                        break;
                }
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        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.+'
        testCompile 'junit:junit:4.12'
        compile 'org.aspectj:aspectjrt:1.8.1'
    }
    
    • 在app module的build.gradle文件中添加AOP配置,可以参考如下配置
    apply plugin: 'com.android.application'
    import org.aspectj.bridge.IMessage
    import org.aspectj.bridge.MessageHandler
    import org.aspectj.tools.ajc.Main
    
    android {
        compileSdkVersion 26
        buildToolsVersion "26.0.3"
        defaultConfig {
            applicationId "com.hj.aopdemo"
            minSdkVersion 14
            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'
            }
        }
    }
    
    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.5",
                             "-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;
                }
            }
        }
    }
    
    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.+'
        compile 'com.android.support.constraint:constraint-layout:1.0.2'
        compile 'org.aspectj:aspectjrt:1.8.1'
        testCompile 'junit:junit:4.12'
        compile project(':aoplib')
    }
    

    通过如上配置,我们就完成了在Android Studio中的项目工程接入AspectJ的配置工作
    这个配置有点繁琐,因此网上其实已经有人写了相应的gradle插件,具体可以参考:
    AspectJ Gradle插件

    2.实例代码

    接下来一个实例包含如何配置,以及基本的操作,如日志输出,异常特殊处理。

    • 异常处理代码
    @Aspect
    public class ExceptionAspect {
    
        @Pointcut("handler(Exception)")
        public void handlerException(){}
        @Pointcut("within(*..MainActivity)")
        public void codeInActivity() {}
    
        @Before("codeInActivity() && handlerException()")
        public void beforeException(JoinPoint joinPoint) {
            if (joinPoint != null) {
                Signature signature =joinPoint.getSignature();
                String className = signature.getDeclaringType().getSimpleName();
                Log.d(className, "beforeException");
            }
        }
    }
    
    • 日志切面代码
    @Aspect
    public class TraceAspect {
        private static final String POINTCUT_METHED = "execution(@com.hj.aoplib.annotation.DebugTrace * *(..))";//含DebugTrace注解的方法,任意返回值,任意名字,任意参数
        private static final String POINTCUT_CONSTRUCTOR = "execution(@com.hj.aoplib.annotation.DebugTrace *.new(..))";//含DebugTrace注解的类,构造函数,任意参数
    
        @Pointcut(POINTCUT_METHED)
        public void methodAnnotatedWithDebugTrace() {}
        @Pointcut(POINTCUT_CONSTRUCTOR)
        public void constructorAnnotatedWithDebugTrace() {}
    
        @Around("methodAnnotatedWithDebugTrace() || constructorAnnotatedWithDebugTrace()")
        public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
            if (joinPoint != null) {
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                String className = methodSignature.getDeclaringType().getSimpleName();
                String methodName = methodSignature.getMethod().getName();
    
                Log.d(className, methodName + "weaveJoinPoint");
                final StopWatch stopWatch = new StopWatch();
                stopWatch.start();
                Object obj = joinPoint.proceed();
                stopWatch.stop();
                //记录方法耗时并输出
                Log.d(className, buildLogMessage(methodName, stopWatch.getTotalTimeMillis()));
                return obj;
            }
            return null;
        }
    
        private static String buildLogMessage(String methodName, long methodDuration) {
            StringBuilder message = new StringBuilder();
            message.append("耗时 --> ");
            message.append(methodName);
            message.append(" --> ");
            message.append("[");
            message.append(methodDuration);
            message.append("ms");
            message.append("]");
    
            return message.toString();
        }
    }
    
    • 影响位置
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        @DebugTrace
        public void test(View v) {
            Log.d("MainActivity", "test-weaveJoinPoint");
            try {
                Thread.sleep(100);
                throw new IllegalArgumentException("Exception");
            } catch (Exception e) {
            }
        }
    }
    

    具体源码请访问GitHub,地址如下https://github.com/hhhhskfk/aopdemo

    JakeWharton/hugo(基于AspectJ实现的AOP日志记录函数库)
    源码

    相关文章

      网友评论

        本文标题:AOP编程

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