【问题标题】:Aspectj doesn't work with kotlinAspectj 不适用于 kotlin
【发布时间】:2017-06-05 08:19:57
【问题描述】:

我想在 kotlin 中使用 aspectj aop,这是我的代码:

我在 annotation.lazy_list 中的注解:

科特林:

 package anotation

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class lazy_list

我的 aspectj aop 类:

@Aspect
class ActiveListAop{

    @Pointcut("execution(@annotation.lazy_list * *(..))")
    fun profile() {

    }

    @Before("profile()")
    fun testModeOnly(joinPoint: JoinPoint) {
        println("123")
    }

}

我的用法:

 @lazy_list
    fun all():List<T>{
        return lazy_obj?.all() as List<T>
    }

当我调用 all() 函数时,没有错误,但不会打印“123”,为什么?

【问题讨论】:

  • 如果尝试更具体的切入点表达式会发生什么?另一种理论是 AOP 编织阶段以某种方式丢失或以不同的顺序应用
  • @MarioArias 什么都没发生。
  • 您使用的是 Kotlin 注释处理器 KAPT 吗?如果没有,从那里开始。
  • @junk 如果您正在使用注释处理器,请告诉我们如何操作。

标签: kotlin aop aspectj


【解决方案1】:

编辑 9-2021 - 有一个不错的 android 更新插件,可以很好地替代我在下面的原始 2018 答案:https://github.com/Ibotta/gradle-aspectj-pipeline-plugin

对于它的价值,我们需要在我们的 android 项目中使用 aspectJ weaving,但真的想迁移到 kotlin,所以我们必须解决这个问题。所以这个线程中使用 spring 或 maven 的解决方案对我们不起作用。这是 android gradle 项目的解决方案,但是,这会破坏增量编译,因此会减慢您的构建时间和/或最终破坏某些东西。这可以让我们度过难关,直到我可以重新考虑我们的架构并逐步淘汰 aspectJ 或(希望)android 开始支持它。

kapt 解决此问题的 OP 的一些答案和 cmets 存在混淆,但 kapt 允许您进行编译时注释处理,而不是编织。也就是说,注解处理器允许您基于注解生成代码,但不允许您将逻辑注入现有代码。

这建立在这篇关于将 aspectJ 添加到 android 的博客之上:https://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android

您的 kotlin 类被编译成字节码,只是进入不同的目录。所以这个解决方案使用相同的过程来编织 java 类,但在 kotlin 类文件上再次运行它

在您的App/build.gradle 顶部添加:

buildscript {
    ext.aspectjVersion = '1.9.1'
    dependencies {
        classpath "org.aspectj:aspectjtools:$aspectjVersion"
    }
}

在你的 App/build.gradle 底部添加:

android.applicationVariants.all { variant ->

// add the versionName & versionCode to the apk file name
variant.outputs.all { output ->
    def newPath = outputFileName.replace(".apk", "-${variant.versionName}.${variant.versionCode}.apk")
    outputFileName = new File(outputFileName, newPath)


    def fullName = ""
    output.name.tokenize('-').eachWithIndex { token, index ->
        fullName = fullName + (index == 0 ? token : token.capitalize())
    }

    JavaCompile javaCompile = variant.javaCompiler

    MessageHandler handler = new MessageHandler(true)
    javaCompile.doLast {
        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)]

        String[] kotlinArgs = ["-showWeaveInfo",
                               "-1.8",
                               "-inpath", project.buildDir.path + "/tmp/kotlin-classes/" + fullName,
                               "-aspectpath", javaCompile.classpath.asPath,
                               "-d", project.buildDir.path + "/tmp/kotlin-classes/" + fullName,
                               "-classpath", javaCompile.classpath.asPath,
                               "-bootclasspath", project.android.bootClasspath.join(
                File.pathSeparator)]

        new Main().run(javaArgs, handler)
        new Main().run(kotlinArgs, 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
            }
        }
    }
}

【讨论】:

  • 即使添加了这段代码,我的 kotlin 类也没有被编织。任何猜测为什么?尽管 java 类工作得非常好,并且被编织在同一个项目中。(我在一个项目中同时拥有 kotlin 和 java 类。)
  • 我建议对脚本底部的构建路径进行实际检查,并验证 kotlin 类文件是否在该目录中。 Gradle 更改了 gradle 5 中构建输出的路径名称,因此它可能会有所不同
【解决方案2】:

对于 Kotlin 中的注解过程,您必须启用并使用 KAPT。如果不通过 Gradle 或 Maven 插件添加此功能,则 Kotlin 代码中的注释处理将无法正常工作。

Kotlin 插件支持 Dagger 或 DBFlow 等注释处理器。为了让他们使用 Kotlin 类,请应用 kotlin-kapt 插件。

另见:

【讨论】:

  • @junk - 是纯 AspectJ 应用程序还是 Spring 应用程序?默认情况下,它可能与 final 类有关
  • @MarioArias 它是一个纯 Aspectj + Kotlin 应用程序,没有 Spring。没有任何java代码
  • 查看我的回答 here 以获得完整的工作示例。
  • @RobbyCornelissen 您提供的链接是错误的(指向这个问题)。我找到了你的另一个有效的答案,这可能是你真正的意思:stackoverflow.com/questions/52087239/…
【解决方案3】:

spring + kotlin + AOP 很好用,直接去http://start.spring.io/ 生成一个支持AOP的项目,你可以在这里看到一段build.gradle...

buildscript {

    ext {
        kotlinVersion = '1.2.30'
        springBootVersion = '2.0.0.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
        classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
    }
}

apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'org.springframework.boot'

...

dependencies {
    compile('org.springframework.boot:spring-boot-starter-aop')
    ...
}

插件 kotlin-spring 使所有类开放 以允许 AOP

然后,只需如下声明您的方面

@Aspect
@Component
class MyAspect {
...

重要:使用 @Aspect@Component 注释来注释你的方面类

小菜一碟! :)

【讨论】:

  • 这仅适用于 Spring 托管 bean,不适用于编译时编织,我认为需要拦截与 List 类的交互
  • 问题是关于 AspectJ 而不是 Spring-AOP。除了仅适用于托管 bean 之外,与 AspectJ 相比,Spring-AOP 还有其他限制。例如,它不支持编译时错误和警告声明。
【解决方案4】:

您可以使用freefair gradle plugin

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "io.freefair.gradle:aspectj-plugin:5.2.1"
  }
}

apply plugin: "io.freefair.aspectj.post-compile-weaving"

【讨论】:

    【解决方案5】:

    所以我认为我有一个很好的(但冗长的)解决方案适用于 Android。在撰写本文时,我使用的是 Gradle 6.7、Android 插件 4.1.0 和 AspectJ 工具 1.9.6。

    问题的要点是:

    • Java 由任务compileDebugJavaWithJavac 编译
    • Kotlin 由任务compileDebugKotlin 编译
    • Gradle 可以运行其中一项任务,也可以同时运行这两项任务,或者都不运行
    • compileDebugJavaWithJavac 取决于 compileDebugKotlin
    • 编织 Kotlin 通常需要 Java 类。

    如果您仔细观察这些要点,您会发现您不能在编译 Kotlin 的过程中进行编织,因为此时可能会缺少 Java 类。如果您这样做,您将收到如下警告:

    警告:不正确的类路径:C:\Users\user\StudioProjects\myapp\app\build\intermediates\javac\debug\classes

    以及诸如

    之类的错误

    错误:无法确定缺少类型 myapp.Foo.Bar 的修饰符

    因此,更好的方法是推迟编织,直到 Java 类被编译。但是,由于您将修改文件而不是作为编译任务的一部分,因此您会丢失增量构建...此外,这种推迟的编织非常难以正确进行 - 请记住,任何编译任务都可能不会实际计划运行!


    真正的解决方案是将编织包装在 Transform 中,这将产生一个带有自己的输入和输出的 Gradle 任务。这意味着您不会污染编译任务的文件,并且这些任务以及此任务将可以说是 UP-TO-DATE-able。这需要相当多的代码,但它是相当明智的!

    首先,把这个放到你的项目中build.gradle.kts:

    buildscript {
        dependencies {
            classpath("org.aspectj:aspectjtools:1.9.6")
        }
    }
    

    这是从构建脚本“内部”运行编织所必需的。如果你想在一个单独的进程中运行编织,这在 Windows 上是个好主意,你需要这个 jar 的路径,你可以通过将以下内容添加到你的应用程序中来获得它build.gradle.kts

    val weaving: Configuration by configurations.creating
    
    dependencies {
        weaving("org.aspectj:aspectjtools:1.9.6")
    }
    

    最后,将 AspectJ 运行时放在类路径中(app build.gradle.kts,注意我只需要在调试版本中编织):

    dependencies {
        debugImplementation("org.aspectj:aspectjrt:1.9.6")
    }
    

    现在,这是我的设置。我有一个本地日志库:cats,其中包含我想要编织的方面。日志语句仅在我的项目中,而不是其他任何地方。另外,我只想在调试版本中运行这些。所以这里是“编织猫”到应用程序中的转换(应用程序的build.gradle.kts):

    class TransformCats : Transform() {
        override fun getName(): String = TransformCats::class.simpleName!!
    
        override fun getInputTypes() = setOf(QualifiedContent.DefaultContentType.CLASSES)
    
        // only look for annotations in app classes
        // transformation will consume these and put woven classes in the output dir
        override fun getScopes() = mutableSetOf(QualifiedContent.Scope.PROJECT)
    
        // ...but also have the rest on our class path
        // these will not be touched by the transformation
        override fun getReferencedScopes() = mutableSetOf(QualifiedContent.Scope.SUB_PROJECTS,
                                                          QualifiedContent.Scope.EXTERNAL_LIBRARIES)
    
        override fun isIncremental() = false
    
        // only run on debug builds
        override fun applyToVariant(variant: VariantInfo) = variant.isDebuggable
    
        override fun transform(invocation: TransformInvocation) {
            if (!invocation.isIncremental) {
                invocation.outputProvider.deleteAll()
            }
    
            val output = invocation.outputProvider.getContentLocation(name, outputTypes,
                                                                      scopes, Format.DIRECTORY)
            if (output.isDirectory) FileUtils.deleteDirectoryContents(output)
            FileUtils.mkdirs(output)
    
            val input = mutableListOf<File>()
            val classPath = mutableListOf<File>()
            val aspectPath = mutableListOf<File>()
    
            invocation.inputs.forEach { source ->
                source.directoryInputs.forEach { dir ->
                    input.add(dir.file)
                    classPath.add(dir.file)
                }
    
                source.jarInputs.forEach { jar ->
                    input.add(jar.file)
                    classPath.add(jar.file)
                }
            }
    
            invocation.referencedInputs.forEach { source ->
                source.directoryInputs.forEach { dir ->
                    classPath.add(dir.file)
                }
    
                source.jarInputs.forEach { jar ->
                    classPath.add(jar.file)
                    if (jar.name == ":cats") aspectPath.add(jar.file)
                }
    
            }
    
            weave(classPath, aspectPath, input, output)
        }
    }
    
    android.registerTransform(TransformCats())
    

    这是上面提到的编织代码:

    // ajc gets hold of some files such as R.jar, and on Windows it leads to errors such as:
    //   The process cannot access the file because it is being used by another process
    // to avoid these, weave in a process, which `javaexec` will helpfully launch for us.
    
    fun weave(classPath: Iterable<File>, aspectPath: Iterable<File>, input: Iterable<File>, output: File) {
        val runInAProcess = OperatingSystem.current().isWindows
        val bootClassPath = android.bootClasspath
    
        println(if (runInAProcess) ":: weaving in a process..." else ":: weaving...")
        println(":: boot class path:  $bootClassPath")
        println(":: class path:       $classPath")
        println(":: aspect path:      $aspectPath")
        println(":: input:            $input")
        println(":: output:           $output")
    
        val arguments = listOf("-showWeaveInfo",
                               "-1.8",
                               "-bootclasspath", bootClassPath.asArgument,
                               "-classpath", classPath.asArgument,
                               "-aspectpath", aspectPath.asArgument,
                               "-inpath", input.asArgument,
                               "-d", output.absolutePath)
    
        if (runInAProcess) {
            javaexec {
                classpath = weaving
                main = "org.aspectj.tools.ajc.Main"
                args = arguments
            }
        } else {
            val handler = MessageHandler(true)
            Main().run(arguments.toTypedArray(), handler)
    
            val log = project.logger
            for (message in handler.getMessages(null, true)) {
                when (message.kind) {
                    IMessage.DEBUG -> log.debug("DEBUG " + message.message, message.thrown)
                    IMessage.INFO -> log.info("INFO: " + message.message, message.thrown)
                    IMessage.WARNING -> log.warn("WARN: " + message.message, message.thrown)
                    IMessage.FAIL,
                    IMessage.ERROR,
                    IMessage.ABORT -> log.error("ERROR: " + message.message, message.thrown)
                }
            }
        }
    }
    
    val Iterable<File>.asArgument get() = joinToString(File.pathSeparator)
    

    (Windows 部分使用weaving 配置;您可能不需要if 的任何部分)

    就是这样!

    编辑:从 AGP 4.2.0 开始,jar.name 不会返回任何有用的信息。目前,我使用了这个脆弱的解决方法:

    if (jar.file.directoriesInsideRootProject().contains("cats")) {
        aspectPath.add(jar.file)
    }
    
    fun File.directoriesInsideRootProject() = sequence {
        var file = this@directoriesInsideRootProject
        while (true) {
            yield(file.name)
            file = file.parentFile ?: break
            if (file == rootProject.projectDir) break
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-11
      • 2020-01-13
      • 1970-01-01
      相关资源
      最近更新 更多