argus-apm-gradle源码分析

argus-apm-gradle工程定义了一个gradle plugin,主要有以下两个作用:

  1. 支持AOP编程,方便ArgusAPM能够在编译期织入一些性能采集的代码;
  2. 通过Gradle插件来管理依赖库,使用户接入ArgusAPM更简单。

argus-apm-gradle使用kotlin语言开发。这里我们假定大家已经熟悉gradle plugin的开发。

AspectJPlugin
esources/META-INF/gradle-plugins/目录下的argusapm.properties指明了plugin的入口:argus-apm-gradle使用kotlin语言开发。这里我们假定大家已经熟悉gradle plugin的开发。 resources/META-INF/gradle-plugins/目录下的argusapm.properties指明了plugin的入口:

implementation-class=com.argusapm.gradle.AspectJPlugin

下面来看下AspectJPlugin这个类的代码:

internal class AspectJPlugin : Plugin {
    private lateinit var mProject: Project
    override fun apply(project: Project) {
        mProject = project
        // 对应在build中argusApmAjxConfig这个extention.
        project.extensions.create(AppConstant.USER_CONFIG, ArgusApmConfig::class.java)

        //公共配置初始化,方便获取公共信息
        PluginConfig.init(project)

        //自定义依赖库管理
        project.gradle.addListener(ArgusDependencyResolutionListener(project))

        project.repositories.mavenCentral()
        // kotlin的扩展
        project.compatCompile("org.aspectj:aspectjrt:1.8.9") 


        if (project.plugins.hasPlugin(AppPlugin::class.java)) {
            project.gradle.addListener(BuildTimeListener())

            val android = project.extensions.getByType(AppExtension::class.java)
            android.registerTransform(AspectJTransform(project))
        }
    }
}

AspectJPlugin 这个类实现了Plugin接口,并实现了apply方法。在apply方法中首先project这个参数将mProject 属性初始化。随后通过project.extensions.create()方法接受了ArgusApmConfig 中定义的参数,create()的第一次参数是使用插件项目的build.gradle中进行参数配置的DSL的名字。然后调用PluginConfig.init() 方法进行初始化。而project.gradle.addListener(ArgusDependencyResolutionListener(project))这句实现了自定义的依赖库管理,addListener()方法接受的参数类型如下:

 * 
    *
  • {@link org.gradle.BuildListener} *
  • {@link org.gradle.api.execution.TaskExecutionGraphListener} *
  • {@link org.gradle.api.ProjectEvaluationListener} *
  • {@link org.gradle.api.execution.TaskExecutionListener} *
  • {@link org.gradle.api.execution.TaskActionListener} *
  • {@link org.gradle.api.logging.StandardOutputListener} *
  • {@link org.gradle.api.tasks.testing.TestListener} *
  • {@link org.gradle.api.tasks.testing.TestOutputListener} *
  • {@link org.gradle.api.artifacts.DependencyResolutionListener} *

这些listener对应gradle编译过程中的各个节。而上面提到的ArgusDependencyResolutionListener实现了DependencyResolutionListener接口,这个是依赖相关的listener。所以ArgusDependencyResolutionListener的beforeResolve会在项目的依赖确定之前调用执行。project.repositories.mavenCentral()将仓库指向了默认的maven central。 project.compatCompile() 这个是通过kotlin的扩展功能为Project临时扩展的函数,最终添加了aspectjrt依赖。如果使用plugin有AppPlugin(即apply plugin: 'com.android.application' ,就是applictaion所在的的工程,而非library)。然后注册了BuildTimeListener用来监听项目编译的时间等。最后获取Extension 的具体类型,并赋值给android,最后android注册了AspectJTransform。

AspectJTransform

接下来讲AspectJTransform,对于Transform类型的task,主要关注transform()函数。关键代码如下:
override fun transform(transformInvocation: TransformInvocation) {
        val transformTask = transformInvocation.context as TransformTask
        LogStatus.logStart(transformTask.variantName)

        //第一步:对输入源Class文件进行切割分组
        val fileFilter = FileFilter(project, transformTask.variantName)
        val inputSourceFileStatus = InputSourceFileStatus()
        InputSourceCutter(transformInvocation, fileFilter, inputSourceFileStatus).startCut()

        //第二步:如果含有AspectJ文件,则开启织入;否则,将输入源输出到目标目录下
        if (PluginConfig.argusApmConfig().enabled && fileFilter.hasAspectJFile()) {
            AjcWeaverManager(transformInvocation, inputSourceFileStatus).weaver()
        } else {
            outputFiles(transformInvocation)
        }

        LogStatus.logEnd(transformTask.variantName)
    }

在这个transform()方法中首先定义了一个TransformTask类型的变量transformTask,然后用transformInvocation的属性对其进行初始化,之后开始输出log。FileFilter是用于指明当前transform这个task 所用到的路径。然后定义并初始化了 InputSourceCutter,之后调用startCut()。下面看下InputSourceCutter这个类:

init {
    if (transformInvocation.isIncremental) {
        LogStatus.isIncremental("true")
        LogStatus.cutStart()

        transformInvocation.inputs.forEach { input ->
            input.directoryInputs.forEach { dirInput ->
                whenDirInputsChanged(dirInput)
            }

            input.jarInputs.forEach { jarInput ->
                whenJarInputsChanged(jarInput)
            }
        }

        LogStatus.cutEnd()
    } else {
        LogStatus.isIncremental("false")
        LogStatus.cutStart()

        transformInvocation.outputProvider.deleteAll()

        transformInvocation.inputs.forEach { input ->
            input.directoryInputs.forEach { dirInput ->
                cutDirInputs(dirInput)
            }

            input.jarInputs.forEach { jarInput ->
                cutJarInputs(jarInput)
            }
        }
        LogStatus.cutEnd()
    }
}

init 方法中,首先判断transformInvocation是否为isIncremental。如果是,就遍历transformInvocation的input列表。然后遍历每个input的directoryInputs和jarInputs,分别调用whenDirInputsChanged()和whenJarInputsChanged()。如果不是isIncremental,则首先将transformInvocation的outputProvider的所有内容删除,之后再遍历transformInvocation.inputs。然后遍历每个input中的directoryInputs和jarInputs,分别调用cutDirInputs()和cutJarInputs()。

InputSourceCutter中定义了一个ThreadPool类型的属性taskManager,ThreadPool类维护了一个定长线程池。

接下来我们来看下init中的四个函数的具体作用,首先我们看whenDirInputsChanged的代码:

private fun whenDirInputsChanged(dirInput: DirectoryInput) {
        taskManager.addTask(object : ITask {
            override fun call(): Any? {
                dirInput.changedFiles.forEach { (file, status) ->
                    fileFilter.whenAJClassChangedOfDir(dirInput, file, status, inputSourceFileStatus)
                    fileFilter.whenClassChangedOfDir(dirInput, file, status, inputSourceFileStatus)
                }

                //如果include files 发生变化,则删除include输出jar
                if (inputSourceFileStatus.isIncludeFileChanged) {
                    logCore("whenDirInputsChanged include")
                    val includeOutputJar = transformInvocation.outputProvider.getContentLocation("include", contentTypes as Set, scopes, Format.JAR)
                    FileUtils.deleteQuietly(includeOutputJar)
                }

                //如果exclude files发生变化,则重新生成exclude jar到输出目录
                if (inputSourceFileStatus.isExcludeFileChanged) {
                    logCore("whenDirInputsChanged exclude")
                    val excludeOutputJar = transformInvocation.outputProvider.getContentLocation("exclude", contentTypes as Set?, scopes, Format.JAR)
                    FileUtils.deleteQuietly(excludeOutputJar)
                    mergeJar(getExcludeFileDir(), excludeOutputJar)
                }
                return null
            }
        })
    }

whenDirInputsChanged会往TaskManager中添加一个新的task,这个task执行的内容是:遍历dirInput中发生变化的目录,然后每个变动的文件执行fileFilter的whenAJClassChangedOfDir()和whenClassChangedOfDir()方法。然后如果属性inputSourceFileStatus.isIncludeFileChanged的值为true,即include files发生变化,则删除include输出jar。如果inputSourceFileStatus的isExcludeFileChanged属性为true,即exclude files发生变化,则重新生成exclude jar到输出目录。
然后看下第二个函数,whenJarInputsChanged的代码如下:

private fun whenJarInputsChanged(jarInput: JarInput) {
        if (jarInput.status != Status.NOTCHANGED) {
            taskManager.addTask(object : ITask {
                override fun call(): Any? {
                    fileFilter.whenAJClassChangedOfJar(jarInput, inputSourceFileStatus)
                    fileFilter.whenClassChangedOfJar(transformInvocation, jarInput)
                    return null
                }
            })
        }
    }

如果参数jarInput 的status属性不等于Status.NOTCHANGED,即jarInput的文件发生了变化。这个时候向taskManager中添加task,这个task依次调用fileFilter的whenAJClassChangedOfJar()和whenClassChangedOfJar()方法。

  接下来看第三个函数。cutDirInputs的代码如下:
private fun cutDirInputs(dirInput: DirectoryInput) {
        taskManager.addTask(object : ITask {
            override fun call(): Any? {
                dirInput.file.eachFileRecurse { file ->
                    //过滤出AJ文件
                    fileFilter.filterAJClassFromDir(dirInput, file)
                    //过滤出class文件
                    fileFilter.filterClassFromDir(dirInput, file)
                }

                //put exclude files into jar
                if (countOfFiles(getExcludeFileDir()) > 0) {
                    val excludeJar = transformInvocation.outputProvider.getContentLocation("exclude", contentTypes as Set, scopes, Format.JAR)
                    mergeJar(getExcludeFileDir(), excludeJar)
                }
                return null
            }
        })
    }

这个函数也想taskManager中添加一个task,这个task 的作用是遍历dirInput的每一个文件,然后对每个文件调用fileFilter的filterAJClassFromDir()和filterClassFromDir()方法。如果getExcludeFileDir中有文件,则调用mergeJar将getExcludeFileDir中的文件merge之后放到excludeJar。
最后看下第四个方法。cutJarInputs的代码如下:

private fun cutJarInputs(jarInput: JarInput) {
        taskManager.addTask(object : ITask {
            override fun call(): Any? {
                fileFilter.filterAJClassFromJar(jarInput)
                fileFilter.filterClassFromJar(transformInvocation, jarInput)
                return null
            }
        })
    }

cutJarInputs也是向taskManager中添加task,task的作用是调用fileFilter的filterAJClassFromJar()和filterClassFromJar()。

分析完InputSourceCutter 的init方法后,回到AspectJTransform的transform方法 。在InputSourceCutter 初始化后调用了startCut(),这个方法调用了TaskManager的startWork(),开启了TaskManager中的所有task.

FileFilter

前面调用了FileFilter的很多方法,现在分析下FileFilter的代码,首先看init()方法,代码如下:

init {
        init()
    }

    private fun init() {
        basePath = project.buildDir.absolutePath + File.separator + AndroidProject.FD_INTERMEDIATES + "/${AppConstant.TRANSFORM_NAME}/" + variantName
        aspectPath = basePath + File.separator + "aspects"
        includeDirPath = basePath + File.separator + "include_dir"
        excludeDirPath = basePath + File.separator + "exclude_dir"
    }

这里实际上指明了gradle编译过程的各个路径,basePath就是位于build目录下的imtermediates目录下。一种典型的路径是:{project root dir}/{module dir}/build/imtermediates/argus_apm_ajx/debug/. 后面的几个path都在这个路径下。 接下来我们看InputSourceCutter 中调用到的几个方法,先看whenAJClassChangedOfDir 这个方法。

fun whenAJClassChangedOfDir(dirInput: DirectoryInput, file: File, status: Status, inputSourceFileStatus: InputSourceFileStatus) {
        if (isAspectClassFile(file)) {
            log("aj class changed ${file.absolutePath}")
            inputSourceFileStatus.isAspectChanged = true
            val path = file.absolutePath
            val subPath = path.substring(dirInput.file.absolutePath.length)
            val cacheFile = File(aspectPath + subPath)

            when (status) {
                Status.REMOVED -> {
                    FileUtils.deleteQuietly(cacheFile)
                }
                Status.CHANGED -> {
                    FileUtils.deleteQuietly(cacheFile)
                    cache(file, cacheFile)
                }
                Status.ADDED -> {
                    cache(file, cacheFile)
                }
                else -> {
                }
            }
        }
    }

whenAJClassChangedOfDir 首先调用isAspectClassFile()这个方法来判断是不是aspect class文件 。isAspectClassFile()首先判断文件是不是class文件。然后通过isAspectClass(FileUtils.readFileToByteArray(file)) 读入文件内容并转换为byte array,判断是否是aspect class文件。isAspectClass方法位于Utils类中,代码如下:

fun isAspectClass(bytes: ByteArray): Boolean {
    if (bytes.isEmpty()) {
        return false
    }

    try {
        val classReader = ClassReader(bytes)
        val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES)
        val aspectJClassVisitor = AspectJClassVisitor(classWriter)
        classReader.accept(aspectJClassVisitor, ClassReader.EXPAND_FRAMES)
        return aspectJClassVisitor.isAspectClass
    } catch (e: Exception) {

    }

    return false
}

上面这个方法用到了ASM框架,ASM是一个JAVA字节码操控框架,能用到动态生成类或者增强已有类的功能。classReader和classWriter都来源于该框架。

classReader类可以直接由字节数组或者class文件间接获得字节码数据,能正常分析字节码,构建出抽象的树在内存中表示字节码。它会调用accept()方法,这个方法接受一个实现了ClassVisitor接口的对象实例作为参数。然后依次调用ClassVisitor接口的各个方法。字节码空间上的偏移被转换成visit 事件在时间上调用的先后,所谓visit事件是指各种不同visit函数的调用,ClassReader知道如何调用各种visit 函数。在这个过程中用户无法对操作进行干涉,所以遍历的算法是确定的,用户可以做的是提供不同的visitor来对字节码树进行不同的修改。ClassVisitor会产生一些子过程,比如visitMethod会返回一个实现了MethodVisitor接口的实例,visitField会返回一个实现了FieldVisitor接口的实例,完成子过程后控制返回到父过程,继续访问下一节点。因此对于ClassReader来说,其内部顺序访问是有一定要求的。实际上用户还可以不通过ClassReader类,自行手工控制这个流程,只要按照一定的顺序,各个visit事件被先后正确的调用,最后就能生成可以被正确加载的字节码。当然获得更大灵活性的同时加大了调整字节码的复杂度。
ClassWriter实现了ClassVisitor接口,而且含有一个toByteArray()函数,返回生成的字节码的字节流,将字节流写会到文件即可生成调整后的class文件,一般它都作为职责链的终点,将所有的visit事件的先后调用(时间上的先后),最终转换为字节码的位置的调整(空间上的前后)。

AspectJClassVisitor扩展子ClassVisitor类,并且重写了visitAnnotation方法,visitAnnotation方法声明:public AnnotationVisitor visitAnnotation(String desc,boolean visible)。参数desc表示被注解修饰的class的描述符,参数visible表示注解在运行时是否可见。重写的visitAnnotation会判断类前有没有"org/aspectj/lang/annotation/Aspect"注解。这也是判断是否是Aspect class的依据。如果类前包含了以上注解,那就是一个Aspect class,那么FileFilter的isAspectClassFile()方法会返回true.whenAJClassChangedOfDir()方法接下来会给inputSourceFileStatus的属性isAspectChanged置为true.标记Aspect class发生了变化,并且设置path和subPath.接下来会根据变化的status,采取下一步的操作。如果是Status.REMOVED,就删除cacheFile.如果是Status.CHANGED,就先删除cacheFild,然后调用cache()方法生成新的cache文件。如果状态是Status.ADDED,则直接调用cache()生成新的cache文件。

接下来看FileFilter的第二个函数,whenClassChangedOfDir 的代码如下:

fun whenClassChangedOfDir(dirInput: DirectoryInput, file: File, status: Status, inputSourceFileStatus: InputSourceFileStatus) {
        val path = file.absolutePath
        val subPath = path.substring(dirInput.file.absolutePath.length)
        val transPath = subPath.replace(File.separator, ".")

        val isInclude = isIncludeFilterMatched(transPath, PluginConfig.argusApmConfig().includes) && !isExcludeFilterMatched(transPath, PluginConfig.argusApmConfig().excludes)

        if (!inputSourceFileStatus.isIncludeFileChanged && isInclude) {
            inputSourceFileStatus.isIncludeFileChanged = isInclude
        }

        if (!inputSourceFileStatus.isExcludeFileChanged && !isInclude) {
            inputSourceFileStatus.isExcludeFileChanged = !isInclude
        }

        val target = File(if (isInclude) {
            includeDirPath + subPath
        } else {
            excludeDirPath + subPath
        })
        when (status) {
            Status.REMOVED -> {
                logCore("[ Status.REMOVED ] file path is ${file.absolutePath}")
                FileUtils.deleteQuietly(target)
            }
            Status.CHANGED -> {
                logCore("[ Status.CHANGED ] file path is ${file.absolutePath}")
                FileUtils.deleteQuietly(target)
                cache(file, target)
            }
            Status.ADDED -> {
                logCore("[ Status.ADDED ] file path is ${file.absolutePath}")
                cache(file, target)
            }
            else -> {
                logCore("else file path is ${file.absolutePath}")
            }
        }
    }

调用 isIncludeFilterMatched和isExcludeFilterMatched,判断include路径和exclude路径是否匹配。如果匹配include路径但不匹配exclude路径,而且 inputSourceFileStatus.isIncludeFileChanged原来的值是false,就把inputSourceFileStatus.isIncludeFileChanged标记为true.如果匹配exclude路径,并且inputSourceFileStatus.isExcludeFileChanged的值为false,就将inputSourceFileStatus.isExcludeFileChanged的值标记为true.接下来生成一个target文件,根据之前的isInclude的值决定生成incude还是exclude文件。最后根据参数status的值,对target文件执行不同的操作。

接下来我们看第三个函数,whenAJClassChangedOfJar的代码如下:

fun whenAJClassChangedOfJar(jarInput: JarInput, inputSourceFileStatus: InputSourceFileStatus) {
        val jarFile = java.util.jar.JarFile(jarInput.file)
        val entries = jarFile.entries()
        while (entries.hasMoreElements()) {
            val jarEntry = entries.nextElement()
            val entryName = jarEntry.name
            if (!jarEntry.isDirectory && isClassFile(entryName)) {
                val bytes = ByteStreams.toByteArray(jarFile.getInputStream(jarEntry))
                val cacheFile = java.io.File(aspectPath + java.io.File.separator + entryName)
                if (isAspectClass(bytes)) {
                    inputSourceFileStatus.isAspectChanged = true
                    when {
                        jarInput.status == Status.REMOVED -> FileUtils.deleteQuietly(cacheFile)
                        jarInput.status == Status.CHANGED -> {
                            FileUtils.deleteQuietly(cacheFile)
                            cache(bytes, cacheFile)
                        }
                        jarInput.status == Status.ADDED -> {
                            cache(bytes, cacheFile)
                        }
                    }
                }
            }
            jarFile.close()
        }
    }

whenAJClassChangedOfJar获取输入的jar文件,并且解析出Jar中的条目,然后遍历这个条目列表。如果该条目不是目录,并且是class文件,就读取class文件的内容,并转换为byte array.输出到变量byte中。然后生成一个cache文件。文件路径是aspect目录的路径+当前这个条目对应的class文件的名字。如果是一个aspect文件,就将inputSourceFileStatus.isAspectChanged标记为true.然后根据jarInput的status来对cache文件进行不同的操作。最后关闭jar文件。

然后看下第四个函数,whenClassChangedOfJar的代码如下:

fun whenClassChangedOfJar(transformInvocation: TransformInvocation, jarInput: JarInput) {
        val outputJar = transformInvocation.outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)

        when {
            jarInput.status == Status.REMOVED -> {
                FileUtils.deleteQuietly(outputJar)
            }
            jarInput.status == Status.ADDED -> {
                filterJar(jarInput, PluginConfig.argusApmConfig().includes, PluginConfig.argusApmConfig().excludes, PluginConfig.argusApmConfig().excludeJars)
            }
            jarInput.status == Status.CHANGED -> {
                FileUtils.deleteQuietly(outputJar)
            }
        }

        //将不需要做AOP处理的文件原样copy到输出目录
        if (!filterJar(jarInput, PluginConfig.argusApmConfig().includes, PluginConfig.argusApmConfig().excludes, PluginConfig.argusApmConfig().excludeJars)) {
            FileUtils.copyFile(jarInput.file, outputJar)
        }
    }

首先,通过 transformInvocation.outputProvider.getContentLocation获取输出jar文件outputJar,然后根据jarInput的status.决定对outputJar的动作。最后对不需要做AOP处理的文件原样copy到输出目录。

接下来,我们继续分析InputSourceCutter 中调用的FileFilter的另外几个方法,filterAJClassFromDir ,filterClassFromDir ,filterAJClassFromJar ,filterClassFromJar 。

filterAJClassFromDir的代码如下:

fun filterAJClassFromDir(dirInput: DirectoryInput, file: File) {
        if (isAspectClassFile(file)) {
            val path = file.absolutePath
            val subPath = path.substring(dirInput.file.absolutePath.length)
            val cacheFile = File(aspectPath + subPath)
            cache(file, cacheFile)
        }
    }

这个函数首先判断参数file是不是aspect class文件,如果是,就用aspect目录+file的文件名组成一个缓存文件路径cacheFile,调用cache方法,将file文件的内容输出到cacheFile中。

filterClassFromDir 的代码如下:

fun filterClassFromDir(dirInput: DirectoryInput, file: File) {
        if (isClassFile(file)) {
            val path = file.absolutePath
            val subPath = path.substring(dirInput.file.absolutePath.length)
            val transPath = subPath.replace(File.separator, ".")

            val isInclude = isIncludeFilterMatched(transPath, PluginConfig.argusApmConfig().includes) &&
                    !isExcludeFilterMatched(transPath, PluginConfig.argusApmConfig().excludes)
            if (isInclude) {
                cache(file, File(includeDirPath + subPath))
            } else {
                cache(file, File(excludeDirPath + subPath))
            }
        }
    }

这个函数首先判断参数file是不是class文件,如果是就从file的决定路径换算出transPath。然后判断transPath路径是否匹配include路径或者exclude路径。如果匹配includes路径,就调用cache方法,将file中的内容,输出到一个新的文件,文件路径是includeDirPath + subPath。如果匹配exclude路径,就调用cache方法,将file中的内容输出到新的文件,不过文件路径是excludeDirPath + subPath。

filterAJClassFromJar的代码如下:

fun filterAJClassFromJar(jarInput: JarInput) {
        val jarFile = JarFile(jarInput.file)
        val entries = jarFile.entries()
        while (entries.hasMoreElements()) {
            val jarEntry = entries.nextElement()
            val entryName = jarEntry.name
            if (!(jarEntry.isDirectory || !isClassFile(entryName))) {
                val bytes = ByteStreams.toByteArray(jarFile.getInputStream(jarEntry))
                val cacheFile = File(aspectPath + File.separator + entryName)
                if (isAspectClass(bytes)) {
                    cache(bytes, cacheFile)
                }
            }
        }
        jarFile.close()
    }

首先从jarInput参数中解析出jar文件,然后遍历jar文件中的条目,如果该条目不是目录,且不是class文件,就将文件内容读取到byte array变量bytes中,用aspect目录+当前条目的名称组成一个路径,并以该路径生成一个cache文件,如果从获取到的bytes判断出这是一个aspect文件,就调用cache方法将bytes的内容输出到cacheFile中,最后关闭jar文件。

filterClassFromJar的代码如下:

 fun filterClassFromJar(transformInvocation: TransformInvocation, jarInput: JarInput) {
        //如果该Jar包不需要参与到AJC代码织入的话,则直接拷贝到目标文件目录下
        if (!filterJar(jarInput, PluginConfig.argusApmConfig().includes, PluginConfig.argusApmConfig().excludes, PluginConfig.argusApmConfig().excludeJars)) {
            val dest = transformInvocation.outputProvider.getContentLocation(jarInput.name
                    , jarInput.contentTypes
                    , jarInput.scopes
                    , Format.JAR)
            FileUtils.copyFile(jarInput.file, dest)
        }
    }

首先通过filterJar判断参数jarInput是否匹配includes,excludes,或者excludeJars路径,如果都不匹配,说明Jar包不需要参与到AJC代码织入,直接拷贝到目标文件目录即可。

以上四个函数名字中都包含了“filter”,在执行目标操作之前都要进行下filter操作。

到目前为止,我们实际上分析完了AspectJTransform类中transform方法的第一步。接下来分析第二步。

transform方法第二步

首先判断PluginConfig.argusApmConfig().enabled 是否为true,然后判断第一步生成的文件是否包含aspect文件。如果两个条件都满足,就用transformInvocation和inputSourceFileStatus 生成一个AjcWeaverManager的对象。并且调用该对象的weave方法,否则就调用outputFiles()方法输出文件内容,这个方法最后再分析。

AjcWeaverManager的默认构造函数有两个参数 TransformInvocation类型和 InputSourceFileStatus类型。这两个参数用来初始化类的两个属性。另外类中还有以下属性:
private val threadPool = ThreadPool()
    private val aspectPath = arrayListOf()
    private val classPath = arrayListOf()

threadPool定义了一个线程池,aspectPath和classPath分别用来存储aspect文件和class文件路径的列表。weave()的代码如下:

fun weaver() {

        System.setProperty("aspectj.multithreaded", "true")

        if (transformInvocation.isIncremental) {
            createIncrementalTask()
        } else {
            createTask()
        }
        log("AjcWeaverList.size is ${threadPool.taskList.size}")

        aspectPath.add(getAspectDir())
        classPath.add(getIncludeFileDir())
        classPath.add(getExcludeFileDir())

        threadPool.taskList.forEach { ajcWeaver ->
            ajcWeaver as AjcWeaver
            ajcWeaver.encoding = PluginConfig.encoding
            ajcWeaver.aspectPath = aspectPath
            ajcWeaver.classPath = classPath
            ajcWeaver.targetCompatibility = PluginConfig.targetCompatibility
            ajcWeaver.sourceCompatibility = PluginConfig.sourceCompatibility
            ajcWeaver.bootClassPath = PluginConfig.bootClassPath
            ajcWeaver.ajcArgs = PluginConfig.argusApmConfig().ajcArgs
        }
        threadPool.startWork()
    }

首先设置系统属性aspectj.multithreaded,允许多线程运行aspectj.如果支持增量编译,就调用createIncrementalTask()方法,创建增量编译任务。否则调用createTask()方法,创建普通的全量编译任务。接下来遍历threadPool的taskList. taskList的内容由createIncrementalTask()或createTask()添加。对TaskList中的每一项都设置好参数之后,最后通过threadPool.startWork()启动线程。

接下来,先看下createTask()方法,代码如下:
 private fun createTask() {
        val ajcWeaver = AjcWeaver()
        val includeJar = transformInvocation.outputProvider.getContentLocation("include", contentTypes as Set, scopes, Format.JAR)
        if (!includeJar.parentFile.exists()) {
            FileUtils.forceMkdir(includeJar.parentFile)
        }
        FileUtils.deleteQuietly(includeJar)
        ajcWeaver.outputJar = includeJar.absolutePath
        ajcWeaver.inPath.add(getIncludeFileDir())
        addAjcWeaver(ajcWeaver)

        transformInvocation.inputs.forEach { input ->
            input.jarInputs.forEach { jarInput ->
                classPath.add(jarInput.file)
                //如果该Jar参与AJC织入的话,则进行下面操作
                if (filterJar(jarInput, PluginConfig.argusApmConfig().includes, PluginConfig.argusApmConfig().excludes, PluginConfig.argusApmConfig().excludeJars)) {
                    val tempAjcWeaver = AjcWeaver()
                    tempAjcWeaver.inPath.add(jarInput.file)

                    val outputJar = transformInvocation.outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes,
                            jarInput.scopes, Format.JAR)
                    if (!outputJar.parentFile?.exists()!!) {
                        outputJar.parentFile?.mkdirs()
                    }

                    tempAjcWeaver.outputJar = outputJar.absolutePath
                    addAjcWeaver(tempAjcWeaver)
                }

            }
        } 
    }

首先,创建一个AjcWeaver对象ajcWeaver,获取includeJar的路径。将includeJar路径下的文件删除后,将ajcWeaver的outputJar 设置为includeJar的绝对路径。将getIncludeFileDir()添加到ajcWeaver的inPath列表中。随后调用addAjcWeaver将AjcWeaver这个task添加到threadPool的task列表中。最后遍历transformInvocation,对其中的每一项遍历其jarInputs列表。将每一个jar文件添加到classPath()中,然后调用filterJar()方法判断该jar文件是否需要参与AJC织入,如果需要参与,创建新的AjcWeaver对象tempAjcWeaver,设置参数后将tempAjcWeaver添加到threadPool的task列表中。

然后再看下createIncrementalTask()方法,其代码如下:

private fun createIncrementalTask() {
        //如果AJ或者Include文件有一个变化的话,则重新织入
        if (inputSourceFileStatus.isAspectChanged || inputSourceFileStatus.isIncludeFileChanged) {
            val ajcWeaver = AjcWeaver()
            val outputJar = transformInvocation.outputProvider?.getContentLocation("include", contentTypes as Set, scopes, Format.JAR)
            FileUtils.deleteQuietly(outputJar)

            ajcWeaver.outputJar = outputJar?.absolutePath
            ajcWeaver.inPath.add(getIncludeFileDir())
            addAjcWeaver(ajcWeaver)

            logCore("createIncrementalTask isAspectChanged: [ ${inputSourceFileStatus.isAspectChanged} ]    isIncludeFileChanged:  [ ${inputSourceFileStatus.isIncludeFileChanged} ]")
        }


        transformInvocation.inputs?.forEach { input ->
            input.jarInputs.forEach { jarInput ->
                classPath.add(jarInput.file)
                val outputJar = transformInvocation.outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)

                if (!outputJar.parentFile?.exists()!!) {
                    outputJar.parentFile?.mkdirs()
                }

                if (filterJar(jarInput, PluginConfig.argusApmConfig().includes, PluginConfig.argusApmConfig().excludes, PluginConfig.argusApmConfig().excludeJars)) {
                    if (inputSourceFileStatus.isAspectChanged) {
                        FileUtils.deleteQuietly(outputJar)

                        val tempAjcWeaver = AjcWeaver()
                        tempAjcWeaver.inPath.add(jarInput.file)
                        tempAjcWeaver.outputJar = outputJar.absolutePath
                        addAjcWeaver(tempAjcWeaver)

                        logCore("jar inputSourceFileStatus.isAspectChanged true")
                    } else {
                        if (!outputJar.exists()) {
                            val tempAjcWeaver = AjcWeaver()
                            tempAjcWeaver.inPath.add(jarInput.file)
                            tempAjcWeaver.outputJar = outputJar.absolutePath
                            addAjcWeaver(tempAjcWeaver)
                            logCore("jar inputSourceFileStatus.isAspectChanged false && outputJar.exists() is false")
                        }
                    }
                }
            }
        }
    }

首先判断AJ或者Include文件是否有变化,如果有一个有变化,就需要重新织入。生成一个新的AjcWeaver对象ajcWeaver,设置参数后将ajcWeaver添加到线程池的task列表中。之后遍历transformInvocation的input列表,对每一项遍历jarInputs 列表,将每一个jarInput的file属性添加到classpath中。随后调用filterJar()判断是否有匹配的includes,exclude path或exclude jar包。如果存在,且有aspect文件发生改变,则新建AjcWeaver对象tempAjcWeaver,设置参数后将其加入到线程池的task列表中。如果没有aspect文件发生改变,并且outputJar文件不存在。同样新建AjcWeaver对象tempAjcWeaver,设置参数后将其添加到线程池的task列表。

AjcWeaver集成自ITask。其call()代码如下:

override fun call(): Any? {
    val log = PluginConfig.project.logger
    val args = mutableListOf(
            "-showWeaveInfo",
            "-encoding", encoding,
            "-source", sourceCompatibility,
            "-target", targetCompatibility,
            "-classpath", classPath.joinToString(File.pathSeparator),
            "-bootclasspath", bootClassPath
    )

    if (!inPath.isEmpty()) {
        args.add("-inpath")
        args.add(inPath.joinToString(File.pathSeparator))
    }
    if (!aspectPath.isEmpty()) {
        args.add("-aspectpath")
        args.add(aspectPath.joinToString(File.pathSeparator))
    }

    if (outputDir != null && !this.outputDir!!.isEmpty()) {
        args.add("-d")
        args.add(outputDir)
    }

    if (outputJar != null && !outputJar!!.isEmpty()) {
        args.add("-outjar")
        args.add(outputJar)
    }

    if (!ajcArgs.isEmpty()) {
        if (!ajcArgs.contains("-Xlint")) {
            args.add("-Xlint:ignore")
        }
        if (!ajcArgs.contains("-warn")) {
            args.add("-warn:none")
        }

        args.addAll(ajcArgs)
    } else {
        args.add("-Xlint:ignore")
        args.add("-warn:none")
    }

    val handler = MessageHandler(true)
    val m = Main()

    args.forEach {
        com.argusapm.gradle.internal.utils.log("$it")
    }

    LogStatus.weaveStart()

    m.run(args.toTypedArray(), handler)
    handler.getMessages(null, true).forEach { message ->
        when (message.kind) {
            IMessage.ABORT, IMessage.ERROR, IMessage.FAIL -> {
                log.error(message.message, message.thrown)
                throw  GradleException(message.message, message.thrown)
            }

            IMessage.WARNING -> {
                log.warn(message.message, message.thrown)
            }

            IMessage.INFO -> {
                log.info(message.message, message.thrown)
            }

            IMessage.DEBUG -> {
                log.debug(message.message, message.thrown)
            }
        }
    }
    return null
}

前面主要是对参数的完善,关键的一句代码是:

m.run(args.toTypedArray(), handler)

这个方法最终会调用Main这个类的run()方法。在这个方法中最终会调用ajc库中的AjdtCommand类中的runCommand().

回到AspectJTransform的transform()方法中来,前面讨论的是如果需要织入的情况。如果不需要织入,则调用outputFiles()方法将输入源输出到目录下,接下来分析outputFiles这个方法。

fun outputFiles(transformInvocation: TransformInvocation) {
    if (transformInvocation.isIncremental) {
        outputChangeFiles(transformInvocation)
    } else {
        outputAllFiles(transformInvocation)
    }
}

如果支持增量编译,就调用outputChangeFiles(),否则调用outputAllFiles().outputChangeFiles的代码如下:

fun outputChangeFiles(transformInvocation: TransformInvocation) {
    transformInvocation.inputs.forEach { input ->
        input.directoryInputs.forEach { dirInput ->
            if (dirInput.changedFiles.isNotEmpty()) {
                val excludeJar = transformInvocation.outputProvider.getContentLocation("exclude", dirInput.contentTypes, dirInput.scopes, Format.JAR)
                mergeJar(dirInput.file, excludeJar)
            }
        }

        input.jarInputs.forEach { jarInput ->
            val target = transformInvocation.outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
            when {
                jarInput.status == Status.REMOVED -> {
                    FileUtils.forceDelete(target)
                }

                jarInput.status == Status.CHANGED -> {
                    FileUtils.forceDelete(target)
                    FileUtils.copyFile(jarInput.file, target)
                }

                jarInput.status == Status.ADDED -> {
                    FileUtils.copyFile(jarInput.file, target)
                }

            }
        }
    }
}

首先遍历transformInvocation的inputs列表,对列表中的每一项,遍历它的directoryInputs列表。对其中每一个dirInput,判断dirInput其中的changedFiles是否为空。如果不为空,就从outputProvider 中获取excludeJar。随后调用mergeJar将dirInput.file中的内容合并到excludeJar中。然后遍历input的每一个jarInput,从outputProvider中获取目标jar包赋值给target,然后根据jarinput的状态分别对target进行操作。

outputFiles中如果不支持增量,就会调用outputAllFiles,其代码如下:

fun outputAllFiles(transformInvocation: TransformInvocation) {
    transformInvocation.outputProvider.deleteAll()

    transformInvocation.inputs.forEach { input ->
        input.directoryInputs.forEach { dirInput ->
            val outputJar = transformInvocation.outputProvider.getContentLocation("output", dirInput.contentTypes, dirInput.scopes, Format.JAR)

            mergeJar(dirInput.file, outputJar)
        }

        input.jarInputs.forEach { jarInput ->
            val dest = transformInvocation.outputProvider.getContentLocation(jarInput.name
                    , jarInput.contentTypes
                    , jarInput.scopes
                    , Format.JAR)
            FileUtils.copyFile(jarInput.file, dest)
        }
    }
}

首先将transformInvocation的outputProvider()的所有内容删除。遍历transformInvocation的inputs,对其中的每一个item input.遍历directoryInputs列表,从outputProvider中取出output的内容,赋值给outputJar,调用mergeJar将dirInput.file中的内容合并到outputJar。然后对遍历每个input的jarInputs列表。对每一项jarInput,根据name,contentTypes,scopes从outputProvider中获取目标文件dest.最终调用FileUtils.copyFile()将jarInput.file拷贝到dest。

到目前为止,已经大体上分析完了transform方法和相关的方法。接下来我们看下经常用到的mergeJar 函数。

fun mergeJar(sourceDir: File, targetJar: File) {
    if (!targetJar.parentFile.exists()) {
        FileUtils.forceMkdir(targetJar.parentFile)
    }

    FileUtils.deleteQuietly(targetJar)
    val jarMerger = JarMerger(targetJar)
    try {
        jarMerger.setFilter(object : JarMerger.IZipEntryFilter {
            override fun checkEntry(archivePath: String): Boolean {
                return archivePath.endsWith(SdkConstants.DOT_CLASS)
            }
        })
        jarMerger.addFolder(sourceDir)
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        jarMerger.close()
    }
}

mergeJar先判断参数targetjar的父目录是否存在,如果不存在,就先创建父目录。然后将targetJar文件删除,以target为参数构建一个JarMerger对象,然后调用jarMerger的setFilter方法。filter是一个JarMerger.IZipEntryFilter的具体实现,重写checkEntry方法。判断传入的文件路径是否以“.class”结尾。最后调用jarMerge的addFolder,最终会触发对文件的读写。

JarMerger的构造函数有一个File类型的参数,JarFile属性代表一个jar文件。JarMerger的init 方法代码如下:

private fun init() {
        if (this.closer == null) {
            FileUtils.forceMkdir(jarFile.parentFile)
            this.closer = Closer.create()
            val fos = this.closer!!.register(FileOutputStream(jarFile))
            jarOutputStream = this.closer!!.register(JarOutputStream(fos))
        }
    }

首先创建jarFile的父目录,创建一个新的Close对象,并赋值给close属性,打开JarFile输出流,同时注册到closer,之后返回给jarOutputStream。

mergerJar中的addFolder首先调用init方法,然后尝试调用addFolderWithPath,addFolderWithPath的代码如下:

@Throws(IOException::class, IZipEntryFilter.ZipAbortException::class)
private fun addFolderWithPath(folder: File, path: String) {
        folder.listFiles()?.forEach { file ->

            if (file.isFile) {
                val entryPath = path + file.name
                if (filter == null || filter!!.checkEntry(entryPath)) {
                    // new entry
                    this.jarOutputStream!!.putNextEntry(JarEntry(entryPath))

                    // put the file content
                    val localCloser = Closer.create()
                    localCloser.use { localCloser ->
                        val fis = localCloser.register(FileInputStream(file))
                        var count = -1
                        while ({ count = fis.read(buffer);count }() != -1) {
                            jarOutputStream!!.write(buffer, 0, count)
                        }
                    }

                    // close the entry
                    jarOutputStream!!.closeEntry()
                }
            } else if (file.isDirectory) {
                addFolderWithPath(file, path + file.name + "/")
            }
        }
    }

首先对folder下的文件进行遍历,如果当前条目是一个目录,那么对该目录继续调用addFolderWithPath()方法,否则就用参数path加上file的name属性主城一个新的path,然后赋值给entryPath。如果filter为空,或者entryPath应该包含在jar归档文件中,就根据entryPath生成一个JarEntry的对象,并添加到jarOutputStream中,之后创建一个closer的对象localCloser,创建一个file的FileInputStream并注册到localCloser,返回输入流给fis。读取file文件的内容,输出到jarOutputStream绑定的enterPath对应的文件中,输出完成后关闭jarOutputStream。

有什么不对的地方,请大家不吝指教。
学习的过程中,参考了网上的资料,感谢先行者的经验总结和无私分享。

参考文献:
360 Argus APM 源码分析(2)—— argus-apm-gradle源码分析

你可能感兴趣的:(argus-apm-gradle源码分析)