学习笔记 Javaagent

使用步骤

1.一个目标被修改的类

你要更新的Class对象 我这里就随便建立了一个


public class TC1 {
    public void fun1(){
        System.out.println("TC1 old function ");
    }
}

2.然后有一个用到这个类的进程
每个进程都有一个pid  线程拿到的也是一样的pid  我这里是开了一个线程去执行 目标类里的方法
就算是用主线程也是一样的效果  不过我是想测试下开了线程后有没有效果
   public static void main(String[] args) {
        String name = ManagementFactory.getRuntimeMXBean().getName();
        String s = name.split("@")[0];
        System.out.println("pid:"+s);
     

       TC1 tc1 = new TC1();
       new Thread(()->{
          String tname=Thread.currentThread().getName();
          while (true){
              try {
                  Thread.sleep(5000);
                  System.out.print(tname+"运行中");
                  tc1.fun1();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
       }).start();
    }

3创建一个类继承ClassFileTransformer 接口 这里重写的方法 并返回的其实是 修改后的字节码,我这里采用的是直接 把新的.class文件变成字节码返回  这个url来源  我利用构造器传入的  传入我本地改好的.class文件路径


public class MyTransformer implements ClassFileTransformer {
    private String url;
    public MyTransformer(String url) {
        this.url=url;
    }
    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer)
            throws IllegalClassFormatException {
        System.out.println("开始做字节码替换的步骤");
        try {
            System.out.println("开始进行修改");
            //1.获取目标类(目标类的信息会封装到CtClass对象中)
          //byte[] re=teacher();
           byte[] re=myclass(url);
            return re;
        }catch (Exception e){
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return null;
    }


    public byte[] myclass(String url) throws IOException {
        FileInputStream  in= new FileInputStream(url);
        byte[] newClassByte= new byte[in.available()];
        in.read(newClassByte);
        return  newClassByte;
    }



}

4.创建Agent

这里的new MyTransformer(args)是 另一个程序 让这个程序执行 方法时候传入的参数 我这里让另一个程序传入的正式 本地用于替换的.class文件的路径

通过分析那个传入的路径  来得到修改类的名字 同时 要对应上这个程序等着被替换的类的对象 Class cs=Class.forName("tests.bytecode.javaagent."+classname)


public class MyAgent {

    public static void premain(String args, Instrumentation inst){
        inst.addTransformer(new MyTransformer(args), true);
    }
    public static void agentmain(String args, Instrumentation inst) throws IOException {
           String url=args;
           String classname=url.replace("\\"," ");
           classname=classname.substring(classname.lastIndexOf(" ")).trim();
           classname=classname.substring(0,classname.lastIndexOf("."));
           System.out.println(classname);

        //指定我们自己定义的 Transformer,在其中利用 Javassist 做字节码替换
        inst.addTransformer(new MyTransformer(args), true);

        try {

           // System.out.println(classname);
            Class cs=Class.forName("tests.bytecode.javaagent."+classname);

            //重定义类并载入新的字节码
            inst.retransformClasses(cs);
            System.out.println("Agent Load Done.");
        } catch (Exception e) {
            System.out.println("agent load failed!");
        }
    }

}

3.要对这个类打包一下 这边的工作就完成了

主要是要拿到那个包的路径

在pom里添加Maven插件 

   <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <archive>
                        <manifest>
<!--                    可以选变成jar后运行主程序        <mainClass>tests.bytecode.javaagent.TC1test</mainClass>-->
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <!--Main方法执行之前可以执行的对象-->
                            <Premain-Class>
                                tests.bytecode.javaagent.DefaultAgent
                            </Premain-Class>
                            <!--Main方法执行之后可以执行的对象-->
                            <Agent-Class>
                                tests.bytecode.javaagent.DefaultAgent
                            </Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
依赖中, Premain-Class Agent-Class 用于指定代理类, Can-Redefine-Classes
否需要重新定义所有类,默认为 false ,可选。 Can-Retransform-Classes 是否需要
retransform ,默认为 false, 可选。依赖添加后,先执行 maven clean 对原有类文件进行
清除,然后执行 maven package 对项目进行打包。
Agent 启动方式
第 一 种 方 式 : 在 main 方 法 执 行 之 前 执 行 , 通 过 java -javaagent:xxx.jar
 主程序启动前 ,会执行 Agent 中的 premain 方法,在此方法中
执行字节码增强。
第二种方式:在程序启动后,通过 attach api 来实现 ( 基于 JDK tools.jar 中的
VisualMachine 去实现 ) ,此时会执行 Agent agentmain 方法,在此方法中可以进行字
节码增强。
打包后
[INFO] --- maven-jar-plugin:3.2.0:jar (default-cli) @ tests ---
[INFO] Building jar: C:\Users\jj\IdeaProjects\tests\target\tests-0.0.1-SNAPSHOT.jar

主要是拿到这个C:\Users\jj\IdeaProjects\tests\target\tests-0.0.1-SNAPSHOT.jar

这里我先测试用另一个程序让上面正在运行的程序的类更新


public class AgentInstrumentTests {
    public static void main(String[] args) throws Exception{
        VirtualMachine vm = VirtualMachine.attach("29568");
        String url="E:\\classloadertest\\TC1.class";
        vm.loadAgent("C:\\Users\\jj\\IdeaProjects\\tests\\target\\tests-0.0.1-SNAPSHOT.jar",url);
    }

}

注意 .attach("29568");这里填的是目标线程的pid 

当这个程序启动后 会根据进程pid找到那个进程  然后那个进程就会执行againt里的方法

然后 那个方法又会调用Transformer里的方法  个人理解 就是把类加载器重写加载新的字节码并重新类加载 

这是  通过 attach api 来实现 ( 基于 JDK tools.jar 中的 VisualMachine 去实现 ) ,此时会执行 Agent agentmain 方法,在此方法中可以进行字 节码增强。
如果需要在那个程序启动前就进行字节码加强 可以 java -javaagent:xxx.jar   这个是你打包号的jar包
也可以在idea里设置vm参数- javaagent:C:\Users\jj\IdeaProjects\tests\target\tests-0.0.1-SNAPSHOT.jar

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值