美文网首页
面向切面编程

面向切面编程

作者: 其勇勇 | 来源:发表于2019-06-18 10:22 被阅读0次

1、定义

Aspect Oriented Programming(AOP),面向切面编程,是一个比较热门的话题。AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。比如我们最常见的就是日志记录了,举个例子,我们现在提供一个查询学生信息的服务,但是我们希望记录有谁进行了这个查询。如果按照传统的OOP的实现的话,那我们实现了一个查询学生信息的服务接口(StudentInfoService)和其实现 类 (StudentInfoServiceImpl.java),同时为了要进行记录的话,那我们在实现类(StudentInfoServiceImpl.java)中要添加其实现记录的过程。这样的话,假如我们要实现的服务有多个呢?那就要在每个实现的类都添加这些记录过程。这样做的话就会有点繁琐,而且每个实现类都与记录服务日志的行为紧耦合,违反了面向对象的规则。那么怎样才能把记录服务的行为与业务处理过程中分离出来呢?看起来好像就是查询学生的服务自己在进行,但却是背后日志记录对这些行为进行记录,并且查询学生的服务不知道存在这些记录过程,这就是我们要讨论AOP的目的所在。AOP的编程,好像就是把我们在某个方面的功能提出来与一批对象进行隔离,这样与一批对象之间降低了耦合性,可以就某个功能进行编程。

--以上概念摘抄自百度百科,方便自己学习--

-----------------------------------------------------------------------------------

那么AOP大概都是解决哪些的问题的呢?我认为就是在很多已知完整的功能逻辑代码(方法)前或者后需要添加额外的相同逻辑的代码,这个时候用AOP比较合适,比如说我需要统计一个APP里大概二十多个功能,哪些功能用户点击率比较高。再或者我需要统计APP里所有涉及A、B类型操作的操作时间,那么我是不是需要在A或者B功能前开始计时,功能代码结束后去统计时长。再或者我们写安卓,有些功能需要用户登录才能进入,如果没有登录,那么就需要跳转到登录界面。

再说一遍,其实就是

在很多已知完整的功能逻辑代码(方法)前或者后需要添加额外的相同逻辑的代码

配置

build.gradle

//这是根目录需要添加的
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.9'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}

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;
            }
        }
    }
}

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

自己再举个例子

比如说我要统计一个APP,用户在哪些界面操作比较多,在APP的首页上有A功能、B功能、C功能。是不是我点击其中任何一个功能要记录一下,一般记录的代码就放在方法开头,类似于

//A功能
public void A(){
        Log.e("log","记录用户操作,点击了首页");

        Log.e("log","A功能逻辑");
}
//B功能
public void B(){
        Log.e("log","记录用户操作,点击了首页");

        Log.e("log","B功能逻辑");
}
//C功能
public void C(){
        Log.e("log","记录用户操作,点击了首页");

        Log.e("log","C功能逻辑");
}

也就是在每个需要统计的地方都写一遍记录用户操作的代码,如果整个工程都这样写是不是有点头疼呢,那么我们下面用AOP的思想来解决这个问题。

首先我们新建文件,进行自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IAspect {
    String value() default "";
}

对这段代码疑惑的同学建议看看这位大神

关于注解的讲解

那我们怎么去理解这个 IAspect 的作用呢,想必童鞋你肯定看过火影,四代火影波风水门有一个瞬间移动的忍术,就是他会在一个地方做一个标记,然后无论在哪里可以瞬间移动到这个地方,人称“黄色闪光”,那么这里的IAspect ,你就可以看成是水门的查克拉,可以做标记。

水门在战斗的时候,是不是在接触敌人的时候,会把自己的查克拉标记出去

这个时候敌人就是功能 A、B、C,那么标记后就成了

//A功能
@IAspect //(切面)
public void A(){
        Log.e("log","记录用户操作,点击了首页");

        Log.e("log","A功能逻辑");
}

//B功能  
@IAspect //(切面)
public void B(){
        Log.e("log","记录用户操作,点击了首页");

        Log.e("log","B功能逻辑");
}
//C功能
@IAspect //(切面)
public void C(){
        Log.e("log","记录用户操作,点击了首页");

        Log.e("log","C功能逻辑");
}

水门在战斗的时候是不是会先在一个地方把螺旋丸准备好,然后瞬移到目标地点进行攻击,就好比我们先把记录的代码写好,然后统一传送到功能代码那里去,下面代码就相当于螺旋丸吧

@Aspect
public class DemoAspect {

    //com.potevio.aopapplication.IAspect : IAspect 的路径
    //* * 表示匹配任意方法
    //(..) 表示匹配任意参数
    @Pointcut("execution(@com.potevio.aopapplication.IAspect * *(..))")
    public void point(){
        
    }

    @Around("point()")
    public void arount(ProceedingJoinPoint joinPoint) throws Throwable {
        Log.e("log","记录用户操作,点击了首页");
        joinPoint.proceed();
    }
}

point(),你把这个方法看作一个点,带有@IAspect的方法看作一个点,比如A()方法,@Around("point()")看字面意思表示point()方法执行前后,其实就是A()执行前后
也就是用户代码执行到A(),发现了我自定义的@IAspect,然后到@Pointcut("execution(@com.potevio.aopapplication.IAspect * *(..))"),得知切点是point()方法,然后发现了 @Around("point()"),开始执行,上面代码首先执行的是Log.e("log","记录用户操作,点击了首页");这个是一个公共的操作,joinPoint.proceed();就是A()方法的执行,大概就是这个样子。
其实不止有@Around,还有

    @Before("point()")
    public void before(JoinPoint point) {
    }

    @Around("point()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
    }

    @After("point()")
    public void after(JoinPoint point) {
        System.out.println("@After");
    }

    @AfterReturning("point()")
    public void afterReturning(JoinPoint point, Object returnValue) {
    }

    @AfterThrowing(value = "point()", throwing = "ex")
    public void afterThrowing(Throwable ex) {
    }

我改一下需求,我要统计首页具体每个功能的使用频率,同时记录每个功能操作需要的时间,我直接给代码

// ***********  IAspect  文件    ***********
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IAspect {
    String value() default "";
    int type() default 0;
}

// ***********  MainActivity  文件    ***********
@IAspect(value = "A功能",type = 1) 
public void A(){
        Log.e("log","A功能逻辑");
}

//B功能  
@IAspect(value = "B功能",type = 1) 
public void B(){
        Log.e("log","B功能逻辑");
}
//C功能
@IAspect(value = "C功能",type = 1) 
public void C(){
        Log.e("log","C功能逻辑");
}

// ***********  DemoAspect 文件    ***********

@Aspect
public class DemoAspect {

    @Pointcut("execution(@com.potevio.aopapplication.IAspect * *(..))")
    public void point(){

    }

    @Around("point()")
    public void arount(ProceedingJoinPoint joinPoint) throws Throwable {
        long beginTime = SystemClock.currentThreadTimeMillis();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        IAspect iAspect = method.getAnnotation(IAspect.class);
        String value = iAspect.value();
        int type = iAspect.type();
        
        joinPoint.proceed();//执行功能逻辑 代码
        long endTime = SystemClock.currentThreadTimeMillis();
        long dx = endTime - beginTime;

        if(type == 1){
            Log.e("qwer","首页" + value + "记录一次,操作了时间 : " + dx);
        }
    }

    @Before("point()")
    public void before(JoinPoint point) {
        
    }

    @After("point()")
    public void after(JoinPoint point) {
    }

    @AfterReturning("point()")
    public void afterReturning(JoinPoint point, Object returnValue) {
    }

    @AfterThrowing(value = "point()", throwing = "ex")
    public void afterThrowing(Throwable ex) {
    }
}
版权声明:个人原创,若转载,请注明出处

相关文章

网友评论

      本文标题:面向切面编程

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