gradle 修改java代码_自定义一个gradle插件动态修改jar包Class文件

本文介绍了如何创建一个自定义的Gradle插件,用于在编译过程中动态修改jar包中的Class文件和Java源文件。通过预埋占位符并替换,实现了对特定字符串的植入。详细讲述了创建插件、扩展配置、遍历文件替换字符串的过程,以及在Android项目中应用此插件的步骤和遇到的问题。
摘要由CSDN通过智能技术生成

动态修改jar包中的class文件,预埋占位符字符串,在编译代码时动态植入要修改的值。记录一下整个过程及踩过的坑。

创建一个Android项目,再创建一个Android library,删掉里面所有代码。添加groovy支持。如:

apply plugin: 'groovy'

sourceCompatibility = 1.8

targetCompatibility = 1.8

buildscript {

repositories {

mavenCentral()

}

}

repositories {

mavenCentral()

}

dependencies {

implementation localGroovy()

implementation gradleApi()

implementation 'com.android.tools.build:gradle:3.1.4'

implementation 'com.android.tools.build:gradle-api:3.1.4'

implementation 'org.javassist:javassist:3.20.0-GA'

}

创建自定义的插件的配置文件: resource/META_INF/gradle-plugins,该目录为固定目录,下面创建自定义的插件配置文件。并将替换原来src/main/java替换为src/main/groovy;在配置文件中添加外部引用的插件名:

implementation-class=me.xp.gradle.placeholder.PlaceholderPlugin

创建完成后整个目录结构为:

aaede863f571

image

准备工作完成,开始创建代码。先定义要扩展的内容格式:

class PlaceholderExtension {

/**

* 要替换的文件

*/

String classFile = ''

/**

* 要替换的模板及值,

* 如:${template}* map->

*{"template","value"}*/

Map values = [:]

/**

* 是否修改项目下的java源文件

*/

boolean isModifyJava = false

}

再将创建的扩展注册到transform中:

class PlaceholderPlugin implements Plugin {

@Override

void apply(Project project) {

//build.gradle中使用的扩展

project.extensions.create('placeholders', Placeholders, project)

def android = project.extensions.getByType(AppExtension)

def transform = new ClassPlaceholderTransform(project)

android.registerTransform(transform)

}

}

在自定义的Transform中遍历项目下的jar包及所有文件,找到要替换的文件及预埋的占位符的字符串,关键代码:

@Override

void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {

super.transform(transformInvocation)

// Transform的inputs有两种类型,一种是目录,一种是jar包,要分开遍历

def outputProvider = transformInvocation.getOutputProvider()

def inputs = transformInvocation.inputs

def placeholder = project.extensions.getByType(Placeholders)

println "placeholders = ${placeholder.placeholders.size()}"

inputs.each { TransformInput input ->

input.directoryInputs.each { DirectoryInput dirInput ->

// 获取output目录

def dest = outputProvider.getContentLocation(dirInput.name,

dirInput.contentTypes, dirInput.scopes,

Format.DIRECTORY)

File dir = dirInput.file

if (dir) {

HashMap modifyMap = new HashMap<>()

dir.traverse(type: FileType.FILES, nameFilter: ~/.*\.class/) {

File classFile ->

def isNeedModify = Utils.isNeedModify(classFile.absolutePath)

if (isNeedModify) {

println " need modify class ${classFile.path}"

File modified = InjectUtils.modifyClassFile(dir, classFile, transformInvocation.context.getTemporaryDir())

if (modified != null) {

//key为相对路径

modifyMap.put(classFile.absolutePath.replace(dir.absolutePath, ""), modified)

}

}

}

modifyMap.entrySet().each {

Map.Entry entry ->

File target = new File(dest.absolutePath + entry.getKey())

println "entry --> ${entry.key} target = $target"

if (target.exists()) {

target.delete()

}

FileUtils.copyFile(entry.getValue(), target)

println "dir = ${dir.absolutePath} "

saveModifiedJarForCheck(entry.getValue(), new File(dir.absolutePath + entry.getKey()))

entry.getValue().delete()

}

}

// 将input的目录复制到output指定目录

FileUtils.copyDirectory(dirInput.file, dest)

}

input.jarInputs.each { JarInput jarInput ->

def jarName = jarInput.name

def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())

if (jarName.endsWith(".jar")) {

jarName = jarName.substring(0, jarName.length() - 4)

}

//生成输出路径

def dest = outputProvider.getContentLocation(jarName + md5Name,

jarInput.contentTypes, jarInput.scopes, Format.JAR)

def modifyJarFile = InjectUtils.replaceInJar(transformInvocation.context, jarInput.file)

if (modifyJarFile == null) {

modifyJarFile = jarInput.file

// println "modifyJarFile = ${modifyJarFile.absolutePath}"

} else {

//文件修改过

println "++++ jar modified >> ${modifyJarFile.absolutePath}"

saveModifiedJarForCheck(modifyJarFile, jarInput.file)

}

//将输入内容复制到输出

FileUtils.copyFile(modifyJarFile, dest)

}

}

在遍历找到要替换的字符串后,直接替换即可:

@Override

FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {

println('* visitField *' + " , " + access + " , " + name + " , " + desc + " , " + signature + " , " + value)

if (!isOnlyVisit) {

modifyMap.each { k, v ->

def matchValue = "\${$k}"

println "matchValue = $matchValue , value = $value --> ${matchValue == value}"

if (matchValue == value) {

value = v

}

}

}

return super.visitField(access, name, desc, signature, value)

}

踩过的坑:

对于要内嵌的扩展,需要动态的添加。如这里在使用时的格式为:

placeholders {

addholder {

//is modify source java file

isModifyJava = true

//modify file name

classFile = "me/xp/gradle/classplaceholder/AppConfig.java"

//replace name and value

values = ['public' : 'AppConfigPubic',

'private': 'AppConfigPrivate',

'field' : 'AppConfigField']

}

addholder {

isModifyJava = false

classFile = "me/xp/gradle/jarlibrary/JarConfig.class"

values = ['config': 'JarConfigPubic']

}

}

由于addholder扩展内嵌在placeholders扩展中,就需要将addholder动态添加扩展,而最外层的placeholders则需要在自定义的PlaceholderPlugin类中静态添加:

project.extensions.create('placeholders', Placeholders, project)

在自定义的placeholders类中动态添加addholder扩展,将闭包作为当作参数传入,这样才能自动将build.gradle中定义的lambda值转成对应的extension对象:

/**

* 添加一个扩展对象

* @param closure

*/

void addholder(Closure closure) {

def extension = new PlaceholderExtension(project)

project.configure(extension, closure)

println " -- > $extension"

placeholders.add(extension)

}

若要修改在java源文件的值,则只需要在generateBuildConfig任务添加一个任务执行即可。创建一个任务并依赖在 generateBuildConfigTask后:

//执行修改java源代码的任务

android.applicationVariants.all { variant ->

def holders = project.placeholders

if (holders == null || holders.placeholders == null) {

println "not add place holder extension!!!"

return

}

ExtensionManager.instance().cacheExtensions(holders.placeholders)

// println "holders = ${holders.toString()} --> ${holders.placeholders}"

//获取到scope,作用域

def variantData = variant.variantData

def scope = variantData.scope

//创建一个task

def createTaskName = scope.getTaskName("modify", "PlaceholderPlugin")

println "createTaskName = $createTaskName"

def createTask = project.task(createTaskName)

//设置task要执行的任务

createTask.doLast {

modifySourceFile(project, holders.placeholders)

}

//设置task依赖于生成BuildConfig的task,在其之后生成我们的类

String generateBuildConfigTaskName = variant.getVariantData().getScope().getGenerateBuildConfigTask().name

def generateBuildConfigTask = project.tasks.getByName(generateBuildConfigTaskName)

if (generateBuildConfigTask) {

createTask.dependsOn generateBuildConfigTask

generateBuildConfigTask.finalizedBy createTask

}

}

要修改java源代码,这里相当于直接修改文件。使用gradle自带的ant工具便非常适用。如:

ant.replace(

file: filPath,

token: matchKey,

value: v

) {

fileset(dir: dir, includes: className)

}

ant还提供常用的正则匹配替换的函数ant.replaceregexp,但由于这里使用占位符,使用$关键字,在java中会自动当作正则的一部分使用,故这里直接使用ant.repace方法,修改完成后直接调用fileset函数即可修改源文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值