某天,在使用不正当途径激活我的IDEA编辑器的时候,发现最后一步需要在idea的启动项配置文件中添加一个参数:-javaagent xxxx.jar
然后我的IDEA就成功激活了,-javaagent
是什么?
javaagent
javaagent
是JDK1.5添加的一个新特性,使用javaagent
可以实现很多类似线上诊断方面的工具,因为它可以在不改源代码的基础上,动态修改代码的行为。像类似btrace
和阿里开源的arthas
都是基于javaagent
所实现的。
talk is cheap show me the code
看了代码,才能真正的了解到javaagent
的威力
首先,编写一个简单的业务逻辑处理类:
public class Service {
public boolean update(){
System.out.println("更新成功");
return true;
}
public boolean insert(){
System.out.println("插入成功");
return true;
}
}
简单的更新和插入操作。
然后编写我们的javaagent
包,javaagent
需要被完整的打包成一个jar
包,因此我们可以先新建一个Maven
项目,然后编写ServiceClassFileTransformer
用来修改刚刚编写的Service
类。
import javassist.*;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
/**
* @author dengchengchao
* @date 2018/12/8 18:38
*/
public class ServiceClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (!"com/dengchengchao/Service".equals(className)) return null;
//className是以/分割,这里需要替换为.否则会报错
className = className.replaceAll("/", ".");
try{
CtClass ctClass= ClassPool.getDefault().get(className);
CtMethod method=ctClass.getDeclaredMethod("update");
//向字节码插入代码
method.insertBefore("{System.out.println(\"代码被插入\");}");
return ctClass.toBytecode();
}catch (NotFoundException | CannotCompileException | IOException e){
e.printStackTrace();
}
return new byte[0];
}
//定义premain,而不是main,使得这个方法在Service的mian方法执行之前执行
public static void premain(String options, Instrumentation ins){
ins.addTransformer(new ServiceClassFileTransformer());
}
}
主要内容便是我们需要实现ClassFileTransformer
接口中的transform
方法,transform
方法中,我们使用了javassist
来修改原本的代码的字节码。
在ServiceClassFileTransformer
类中,还包含一个premain
方法,性质和main
一样,都是作为入口函数。在JDK1.6后还添加了一个方法
public static void agentmain(String agentArgs, Instrumentation inst){
}
用来以Attach
的方式载入,用于在Java程序启动后执行,这里不做过多研究。
编写完ClassFileTransformer
的实现以后,还需要最后一步,添加MANIFEST
文件,我们可以手动指定,也可以使用Maven配置:
手动指定的话,我们可以在src
目录下添加一个META-INF
文件夹,然后创建一个MANIFEST.MF
文件
在文件中指定Agent
的启动类:
Mainfest-Version:1.0
Agent-Class:com.dengchengchao.utils.ServiceClassFileTransformer
Premian-Class:com.dengchengchao.utils.ServiceClassFileTransformer
Can-Redefine-Classes:true
Can-Retransform-Classes:true
这里最主要的便是指定Premain-Class
,就像普通的NABUFEST.MF
文件是用来指定main
入口一样。
如果编写的是static void premain
方法作为主要入口,那就指定Prmain-Class
,
如果是static void agentmain
,那就指定Agent-Class
如果不想手动编写,想使用Maven
配置的话,可以在POM
中添加如下配置:
<plugin>
<artifactId> maven-assembly-plugin </artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<Premain-Class>
com.dengchengchao.util.ServiceClassFileTransformer
</Premain-Class>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
/execution>
</executions>
</plugin>
然后打包成jar包,名字修改为aop.jar
。
接下来回到编写Service
类的项目工程中,测试Service
public static void main(String args[]) {
Service service = new Service();
service.update();
}
在启动配置中,添加VM启动项:-javaagent F:\utils\target\aop.jar
IDEA添加方法为选择相关项目,
Edit Configurations
,添加VM options即可,
运行:我们会得到如下结果:
代码被插入
更新成功
也就是说,我们在没有修改Service
源码的情况下,却修改了它的方法。
回想IDEA的破解配置,猜想他可能是通过拆包找到了IDEA的验证配置方法,直接返回验证成功。