Aspectj不适用于kotlin

时间:2017-06-05 08:19:58

标签: kotlin aop aspectj

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

我在annotation.lazy_list中的注释:

科特林:

 package anotation

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

my aspectj aop class:

@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”,为什么?

6 个答案:

答案 0 :(得分:5)

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

  

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

另见:

答案 1 :(得分: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 注释

注释您的方面类

一块蛋糕! :)

答案 2 :(得分:1)

检查@RobbyCornelissen的答案here。 我发现它有效,并感谢他。

答案 3 :(得分:1)

对于它的价值,我们需要在我们的android项目中进行AspectJ编织,但确实想迁移到Kotlin,所以我们不得不解决这个问题。因此,该线程中使用spring或maven的解决方案对我们不起作用。这是android gradle项目的解决方案,但是,这将中断增量编译,因此会减慢您的构建时间和/或最终破坏某些内容。直到我可以重新考虑我们的架构并逐步淘汰AspectJ为止,或者(希望如此)Android开始支持它。

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

在此博客的基础上,它向Android添加了AspectJ 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
            }
        }
    }
}

答案 4 :(得分:1)

您可以使用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 :(得分:0)

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

问题的要点是:

  • Java是由任务compileDebugJavaWithJavac
  • 编译的
  • 科特林是根据任务compileDebugKotlin编译的
  • Gradle可以运行这些任务之一,也可以同时运行,或者都不运行
  • compileDebugJavaWithJavac取决于compileDebugKotlin
  • 编织Kotlin通常需要Java类。

如果仔细研究这些要点,您会发现您无法在编译Kotlin时进行编织,因为此时Java类可能会丢失。如果这样做,您将收到诸如以下警告:

WARN:错误的类路径:C:\ Users \ user \ StudioProjects \ myapp \ app \ build \ intermediates \ javac \ debug \ classes

以及诸如

之类的错误

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

所以更好的方法是推迟编织,直到编译Java类为止。但是,由于您要在编译任务中修改文件 not ,您会丢失增量构建...此外,这种推迟的编织非常难于解决–请记住,所有编译任务都可能无法解决。实际计划运行!


真正的解决方案是将编织包裹在Transform中,这将产生具有自己的输入和输出的Gradle任务。这意味着您将不会污染编译任务的文件,并且这些任务以及该任务将可以使用UP-TO-DATE。这需要很多代码,但这很明智!

首先,将其放入您的项目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运行时放在类路径中(应用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的任何一部分)

就这样!