简单了解 JavaAgent

某天,在使用不正当途径激活我的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的验证配置方法,直接返回验证成功。