首页 > 编程语言 >Java Instrumentation

Java Instrumentation

时间:2022-10-20 16:35:08浏览次数:91  
标签:Instrumentation Java String import org public asm

文章目录

 

一、前言

日常开发中避免不了,修改了代码重新启动应用去验证问题,如果没有热部署,则需要每次修改完就去编译代码再启动,这样子的操作看似简单,
但很耗时,特别电脑配置不高,或者项目比较大的情况

二、热部署初识

热部署其实就是在代码运行时去加载我们动态现在修改过的代码到服务器上,诸如 SpringBoot的devtools插件,jrebel插件等等,都是热部署的插件

三、Java Instrumentation

自Jdk5开始,就引入了 Java Instrumentation,它可以通过 addTransformer 方法设置一个 ClassFileTransformer,可以在这个 
ClassFileTransformer 实现类的转换

jdk5提供的Instrumentation 是静态的,基本思路就是在java程序启动前去加载一个代理(javaagent),这个javaagent是一个jar,然后需要编写
一个premain() 方法,然后记录在MANIFEST.MF中,在运行main()方法前,会先运行premain()方法里的逻辑
整个代理的过程基本是:先将代理类打成一个jar,然后在主程序中加上 -javaagent 的参数,参数的值是代理jar的全路径

四、Java Instrumentation 静态代码示例

4.1、编写permain()方法(示例):

import java.lang.instrument.Instrumentation;

/**
 * @author LGY
 * @date 2021/10/25 22:46
 */
public class TestAgent {


    public static void premain(String agentArgs, Instrumentation instrumentation){
        System.out.println("agent start");

        System.out.println(agentArgs);

    }

4.2、在MANIFEST.MF中指定premain()的路径

Manifest-Version: 1.0
Premain-Class: com.lgydojava.jvmdemo.agent.TestAgent

4.3、在maven中加入如下插件,在maven中指定Premain-Class的目的是:在maven将程序打成jar时,会替换掉MANIFEST.MF中的内容,所以这样要指定Premain-Class的值

<build>
  <finalName>my-javaagent</finalName>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <configuration>
        <archive>
          <manifestEntries>
            <Premain-Class>com.lgydojava.jvmdemo.agent.TestAgent</Premain-Class>
            <Can-Redefine-Classes>true</Can-Redefine-Classes>
            <Can-Retransform-Classes>true</Can-Retransform-Classes>
          </manifestEntries>
        </archive>
      </configuration>
    </plugin>
  </plugins>
</build>

4.4、在主程序中加入 -javaagent 启动参数
在这里插入图片描述

4.5、启动主程序 main()方法
在这里插入图片描述

五、Java Agent 示例 —— attach的使用

Java Instrumentation 动态加载被修改的代码 —— attach
业内很出名的arthas 也是利用了attach的原理来实现的

5.1、修改pom文件

<dependencies>

		<dependency>
			<groupId>org.ow2.asm</groupId>
			<artifactId>asm</artifactId>
			<version>9.1</version>
		</dependency>

		<dependency>
			<groupId>org.ow2.asm</groupId>
			<artifactId>asm-commons</artifactId>
			<version>8.0</version>
		</dependency>


	</dependencies>

	<build>
		<finalName>my-attach-agent-project</finalName>
		<plugins>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<version>3.2.0</version>
				<configuration>
					<archive>

						<manifestEntries>
							<Agent-Class>com.lgydojava.AgentMain</Agent-Class>
<!--							<Premain-Class>AgentMain</Premain-Class>-->
							<Can-Redefine-Classes>true</Can-Redefine-Classes>
							<Can-Retransform-Classes>true</Can-Retransform-Classes>
						</manifestEntries>
					</archive>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<version>3.2.4</version>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>shade</goal>
						</goals>
						<configuration>
							<relocations>
								<relocation>
									<pattern>org.ow2.asm</pattern>
									<shadedPattern>me.ya.agent.hidden.org.objectweb.asm</shadedPattern>
								</relocation>
							</relocations>
						</configuration>
					</execution>
				</executions>

			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.7.0</version>
				<configuration>
					<source>8</source>
					<target>8</target>
				</configuration>

			</plugin>

		</plugins>

 

5.2、创建AgentMain类,实现在每个函数进入和结束时都打印一行日志,实现调用过程的追踪的效果


import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;

import static org.objectweb.asm.Opcodes.ASM7;

/**
 * @author LGY
 * @date 2021/10/26 23:03
 */
public class AgentMain {


    public static class MyMethodVisitor extends AdviceAdapter {

        protected MyMethodVisitor( MethodVisitor methodVisitor,int access, String name, String desc) {
            super(ASM7,methodVisitor,access, name, desc);
        }

        @Override
        protected void onMethodEnter(){
//            mv.
            mv.visitIntInsn(BIPUSH,50);
            mv.visitInsn(IRETURN);
        }
    }

    public static class MyClassVisitor extends ClassVisitor {
        public MyClassVisitor(ClassVisitor classVisitor) {
            super(ASM7, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions){

            MethodVisitor mv = super.visitMethod(access,name,descriptor,signature,exceptions);
            System.out.println(name);
            if ("foo".equalsIgnoreCase(name)) {
                return new MyMethodVisitor(mv,access,name,descriptor);
            }

            return mv;
        }
    }

    public static class MyClassFileTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            System.out.println("className:"+className);
//            if (!"com.lgydojava.jvmdemo.MyTestMain".equalsIgnoreCase(className)) {
//                return classfileBuffer;
//            }

            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new MyClassVisitor(cw);
            cr.accept(cv,ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return cw.toByteArray();
        }
    }

    public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
        System.out.println("agentmain called");
        inst.addTransformer(new MyClassFileTransformer(),true);
        Class classes[] = inst.getAllLoadedClasses();

        for (int i = 0; i < classes.length; i++) {
//            inst.retransformClasses(classes[i]);
            Class aClass = classes[i];
            String name = aClass.getName();
            System.out.println(name);
            if (name.equals("com.lgydojava.jvmdemo.MyTestMain")) {
                System.out.println("Reloading: " + name);
                inst.retransformClasses(aClass);
                break;
            }
        }


    }


}

5.5、 在MANIFEST.MF中指定Agent类

Manifest-Version: 1.0
Can-Redefine-Classes: true
Agent-Class: com.lgydojava.AgentMain
Can-Retransform-Classes: true
Permissions: all-permissions

5.6、 将程序打成jar包

5.7、 创建 attach类 MyAttachMain.java,PID是运行程序的ID,window可以在任务管理器中查看,loadAgent是agent.jar的全路径

import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

import java.io.IOException;

/**
 * @author LGY
 * @date 2021/10/26 23:31
 */
public class MyAttachMain {


    public static void main(String[] args) throws IOException, AttachNotSupportedException {
        VirtualMachine vm = VirtualMachine.attach(PID);
        try {
            vm.loadAgent("E:\\Idea_workspace\\jvmdemo\\target\\my-attach-agent-project.jar");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            vm.detach();
        }
    }


}

5.8、编写MyTestMain 主程序,并运行起来

import java.util.concurrent.TimeUnit;

/**
 * @author LGY
 * @date 2021/10/27 22:39
 */
public class MyTestMain {

    public static void main(String[] args) throws InterruptedException {
        while (true){
            System.out.println(foo());
            TimeUnit.SECONDS.sleep(3);
        }
    }

    private static int foo() {
    	//将原来返回100的,改为返回50
        return 100;
    }
}

5.9、运行验证

 输入MyTestMain 的PID,然后运行 MyAttachMain ,查看结果,可以看到原来是返回100的,当我们修改后,就返回了50
  • 1

在这里插入图片描述

标签:Instrumentation,Java,String,import,org,public,asm
From: https://www.cnblogs.com/yuarvin/p/16810353.html

相关文章

  • java实现调用http请求的几种常见方式
    一、概述在实际开发过程中,我们经常需要调用对方提供的接口或测试自己写的接口是否合适。很多项目都会封装规定好本身项目的接口规范,所以大多数需要去调用对方提供的接口或......
  • Java I/O(3):NIO中的Buffer
    您好,我是湘王,这是我的51CTO博客,欢迎您来,欢迎您再来~​​之前在调用Channel的代码中,使用了一个名叫ByteBuffer类,它是Buffer的子类。这个叫Buffer的类是专门用来解决高速设备与......
  • JMeter 扩展开发:自定义 Java Sampler
    JMeter内置支持了一系列的常用协议,例如HTTP/HTTPS、FTP、JDBC、JMS、SOAP和TCP等,可以直接通过编写脚本来支持相关协议的测试场景。除了这些协议之外,用户也可能需要进行......
  • java spring boot 项目启动配置由.properties改为.yml。failed to configure a dataso
    因为yml的文件结构可以少打字,就想着把.properties的配置文件改为.yml的,结果发现坑还不少,在此记录一下。1、安装相应的plugins    2、添加相应的文件名 3、设......
  • java反射之基础
    1.加载并获取该Class对象可以通过三种方式:1.1:Class.forName(类的全路径) Classcl=Class.forName("Demo1.GetClass");  1.2:实例对象.getClass()方法 ......
  • 如何利用Java在Word中创建表格
    当我们在编辑Word文档时,如果遇到大量数据需要体现,可以选择直接在Word文档中创建表格。将数据应用于表格内,不仅能够简化文档语言,而且也可以使数据内容更加清晰、直观。下面......
  • java学习笔记40
    面向对象static关键字详解静态属性packageoopzong.oop.opp6;//static:在类中使用修饰成员变量,在方法就是成员方法 静态方法和静态属性publicclassStudent{​ ......
  • Java并发编程学习8-同步工具类
    同步工具类同步工具类可以是任意一个对象,只要它根据其自身的状态来协调线程的控制流。阻塞队列可以作为同步工具类,类似地还有信号量(Semaphore)、栅栏(Barrier)以及闭锁(Latch)......
  • 记事本写java程序带汉字时,编辑后发生错误解决办法
    程序中带有汉字,编辑时,出现在notpad中将字符集改为,nasi......
  • 【JavaWeb】一文搞懂Response
    @[Toc]1Response继承体系response和request一样2Response响应2.1响应行方法名方面类型方法作用setStatus(intsc)void设置响应状态码2.2响应头方法名方法类型方法作用se......