首页 > 解决方案 > 如何每次使用 gradle 制作方面编织 kotlin 代码

问题描述

我正在做一些 AOP 研究并使用 gradle + aspectJ 来编织我的代码。所以我做了一些演示,将aspectJtools添加到顶级build.gradle。

dependencies {
    ...
    classpath 'org.aspectj:aspectjtools:1.9.5'
}

并在 app/build.gradle 中添加任务以编织代码:

variants.all { variant ->
    JavaCompile javaCompile
    if (variant.hasProperty('javaCompileProvider')) {
        //android gradle 3.3.0 +
        javaCompile = variant.javaCompileProvider.get()
    } else {
        javaCompile = variant.javaCompile
    }
    def buildType = variant.buildType.name

    javaCompile.doLast {

        MessageHandler handler = new MessageHandler(true)

        String[] javaArgs = [
                "-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)
        ]
        new Main().run(javaArgs, handler)

        String[] kotlinArgs = [
                "-showWeaveInfo",
                "-1.8",
                "-inpath", project.buildDir.path + "/tmp/kotlin-classes/" + buildType,
                "-aspectpath", javaCompile.classpath.asPath,
                "-d", project.buildDir.path + "/tmp/kotlin-classes/" + buildType,
                "-classpath", javaCompile.classpath.asPath,
                "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)
        ]
        new Main().run(kotlinArgs, 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:
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break
            }
        }
    }
}

然后我添加我的方面,如:

@Aspect
public class AspectInCommon extends BaseAspect {

    @Pointcut("execution(* com.example.cheng.test.MainActivityKt.on**(..))")
    public void kotlinMainOn() {
    }

    @After("kotlinMainOn()")
    public void hookKotlinMain(JoinPoint joinPoint) throws Throwable {
        log(joinPoint.getSignature().toLongString());
    }

    ...
}

我的 MainActivity 看起来像:

    class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ...
    }
}

通过这种方式,我确实将方面融入了我的代码。这是一些输出: 在此处输入图像描述 它第一次工作正常,直到我再次单击调试底部,然后所有输出都不再被触发。然后经过几次测试,我发现如果我的文件之一在 kotlin 中,它只会在干净构建后编织。所以我尝试将 mainActivity 和 aspect 文件更改为 java 或 kotlin。这是测试结果:

MainActivity language       Aspect language        Weave results
Java                        Java                   Weave every time
Java                        Kotlin                 Weave after clean build *
Kotlin                      Java                   Weave after clean build
Kotlin                      Kotlin                 Weave after clean build

*:我将方面文件放在子模块中,它在干净构建后的第二次构建中崩溃。错误信息是:

java.lang.NoSuchMethodError: No static method aspectOf()

我想知道如何每次都制作方面编织 kotlin 代码,而不是在干净构建后只制作一次,因为我真实项目的大多数代码都是基于 kotlin 的。不简单地使用插件是完美的。非常感谢。

标签: androidkotlingradleaspectj

解决方案


我既不使用 Gradle(而是 Maven)也不使用 Kotlin,但我对多种 JVM 语言或多个编译步骤的经验是,在单独的模块中执行每个编译步骤是有意义的。

  1. 所以你可以有一个 Java + Kotlin 应用程序类模块,用普通的 Java/Kotlin 编译器编译它。
  2. 然后你有一个用 AspectJ 编译器编译的方面模块。
  3. 最后,有一个使用 AspectJ 进行二进制编织的模块,使用 inpath 上的第一个模块和 aspect 路径上的第二个模块。

这使得构建更简单,更稳定。其他优点是您拥有一个原始的、未编织的应用程序模块和一个方面增强的模块。根据具体情况,您可以在应用程序中使用前者或后者,例如,如果您编织不应该总是使用的调试或跟踪方面。最后但并非最不重要的一点是,单独的方面模块使方面库可能可重用。


推荐阅读