源代码在哪里找(现在哪里有看)
首发

源代码在哪里找(现在哪里有看)

优质
请用语音读文章

1. 前言

为什么会接触javaAgent呢?

这起源于笔者最近在读Dubbo的源码。Dubbo有一个很有意思的功能——SPI。它可以根据运行时的URI参数。自适应的调用特定的实现类。大致的原理其实也能猜到。无非就是生成一个代理类。反射解析URI参数里的值。然后再调用对应的实现类。虽然大概可以猜到实现原理。但毕竟只是猜想。抱着科学严谨的精神。还是想看看Dubbo的实现源码。此时就有了一个想法。能不能把Dubbo生成的代理对象的Class类Dump下来。然后反编译看看它的源码呢?

理论上是完全可行的。阿里有一个很好用的开源工具Arthas。它的jad命令就支持对JVM已经加载的类进行反编译查看源码。笔者把Arthas项目源码down下来了。查看以后发现。需要用到JavaAgent技术。

2. JavaAgent规范

在JDK1.5以后。我们可以使用JavaAgent技术。以「零侵入」的方式对Java程序做增强。例如阿里云的Arms应用监控服务。就可以通过JavaAgent的方式接入一个探针。它会把应用的运行数据上报到阿里云。开发者可以在后台查看到应用的运行数据。这种方式。不需要我们对应用做任何改动。就可以轻松实现应用监控。

JavaAgent是一种规范。它分为两类:主程序运行前Agent、主程序运行后Agent。它可以在JVM加载Class文件前。对字节码做修改。甚至允许修改已经加载过的Class。这样我们就可以对应用做增强、以及实现代码热部署。

主程序运行前Agent的步骤:

1、编写Agent类。该类必须有静态方法premain()。

publicclassMyAgentClass{

	//JVM优先执行该方法
	publicstaticvoidpremain(StringagentArgs,Instrumentationinst){
		System.err.println("mainbefore...");
	}

	publicstaticvoidpremain(StringagentArgs){
		System.err.println("mainbefore...");
	}
}

2、在resources/META-INF目录下编写MANIFEST.MF文件。指定Premain-Class。然后将程序打成Jar包。

Manifest-Version:1.0
Can-Redefine-Classes:true
Can-Retransform-Classes:true
Premain-Class:top.javap.agent.MyAgentClass
//注意。这里必须空一行

使用Maven构建程序时。也可使用如下配置。

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>top.javap.agent.MyAgentClass</Premain-Class>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>

3、启动目标程序时。指定JVM参数。如下:

java-javaagent:agent-1.0-SNApsHOT.jarJavaapp

主程序运行后Agent的步骤:

这种是针对已经运行的JVM进程。我们可以通过attach机制。启动一个新的JVM进程发送指令给它执行。

1、编写Agent类。该类必须有静态方法agentmain()。

publicclassMyAgentClass{

	publicstaticvoidagentmain(StringagentArgs,Instrumentationinst){
		System.err.println("mainafter...");
	}
}

2、在resources/META-INF目录下编写MANIFEST.MF文件。指定Premain-Class。然后将程序打成Jar包。

Manifest-Version:1.0
Can-Redefine-Classes:true
Can-Retransform-Classes:true
Agent-Class:top.javap.agent.MyAgentClass
//注意。这里必须空一行

3、编写attach程序。启动并attach到目标JVM进程。

publicstaticvoidmain(String[]args)throwsException{
Virtualmachinevm=VirtualMachine.attach("8080");
vm.loadAgent("/dev/agent.jar");
}

3. 相关组件

3.1 Instrumentation

编写的AgentClass类必须有premain()方法。其中一个比较重要的参数就是Instrumentation。它是JavaAgent技术用到的主要API。接口定义如下:

publicinterfaceInstrumentation{
/**
	*添加Class文件转换器。底层采用数组存储
	*JVM加载Class文件前。需要依次经过转换
	*@paramtransformer
	*@paramcanRetransform是否允许转换
	*/
voidaddTransformer(ClassFileTransformertransformer,booleancanRetransform);
voidaddTransformer(ClassFileTransformertransformer);

//删除Class文件转换器
booleanremoveTransformer(ClassFileTransformertransformer);
booleanisRetransformClassesSupported();

//重新转换Class
voidretransformClasses(Class<?>...classes)throwsUnmodifiableClassException;
booleanisRedefineClassesSupported();

//重新定义Class。热更新
voidredefineClasses(ClassDefinition...definitions)
throwsClassNotFoundException,UnmodifiableClassException;
booleanisModifiableClass(Class<?>theClass);
@SuppressWarnings("rawtypes")
Class[]getAllLoadedClasses();
@SuppressWarnings("rawtypes")
Class[]getInitiatedClasses(ClassLoaderloader);
//获取对象大小
longgetObjectSize(ObjectobjectToSize);
voidappendToBootstrapClassLoaderSearch(JarFilejarfile);
voidappendToSystemClassLoaderSearch(JarFilejarfile);
booleanisNativeMethodPrefixSupported();
voidsetNativeMethodPrefix(ClassFileTransformertransformer,Stringprefix);
}

重要的方法笔者已经写上注释了。本文会用到的方法主要是addTransformer()。它可以用来添加Class转换器。JVM在加载Class前。会先经过这些转换器进行加工。

3.2 ClassFileTransformer

Class文件转换器。JVM加载某个Class前。会先经过它转换。我们可以在这里去修改字节码以达到功能增强的目的。它只有一个方法transform():

publicinterfaceClassFileTransformer{

/**
	*转换Class
	*@paramloader类加载器
	*@paramclassName类名
	*@paramclassBeingRedefined原始Class
	*@paramProtectionDomain
	*@paramclassfileBufferClass文件字节数组
	*/
	byte[]transform(ClassLoaderloader,
StringclassName,
Class<?>classBeingRedefined,
ProtectionDomainprotectionDomain,
byte[]classfileBuffer)
throwsIllegalClassFormatException;
}

本文主要用到的就是classfileBuffer。有了Class的字节数组。只要把它导出到磁盘。通过IDEA反编译就能看到源码了。

4. 实战

【需求】

支持将任意Java对象的Class文件导出到磁盘。通过反编译查看源码。包括动态生成的类。

【实现】

1、编写InstrumentationHolder。持有Instrumentation实例。后续操作全靠它。

publicclassInstrumentationHolder{
	privatestaticInstrumentationINSTANCE;

	publicstaticvoidinit(Instrumentationins){
		INSTANCE=ins;
	}

	publicstaticInstrumentationget(){
		if(INSTANCE==null){
			thrownewRuntimeException("检查-javaagent配置");
		}
		returnINSTANCE;
	}
}

2、编写MyAgentClass。保存Instrumentation实例。

publicclassMyAgentClass{

	publicstaticvoidpremain(StringagentArgs,Instrumentationinst){
		System.err.println("mainbefore...");
		InstrumentationHolder.init(inst);
	}
}

3、编写ClassDumpTransformer。获取Class文件字节数组。导出到磁盘。

publicclassClassDumpTransformerimplementsClassFileTransformer{
	privatefinalFilefile;
	privatefinalSet<Class<?>>classes=newHashSet<>();

	publicClassDumpTransformer(Stringpath,Class<?>...classes){
		this.file=newFile(path);
		this.classes.addAll(Arrays.asList(classes));
	}

	@Override
	publicbyte[]transform(ClassLoaderloader,StringclassName,Class<?>classBeingRedefined,ProtectionDomainprotectionDomain,byte[]classfileBuffer)throwsIllegalClassFormatException{
		if(classes.contains(classBeingRedefined)){
			FileUtil.writeBytes(classfileBuffer,file);
		}
		returnnull;
	}
}

4、编写ClassUtil工具类。支持导出Class文件。

publicclassClassUtil{

	publicstaticvoidclassDump(Class<?>c,Stringpath){
		ClassDumpTransformertransformer=newClassDumpTransformer(path,c);
		Instrumentationinst=InstrumentationHolder.get();
		inst.addTransformer(transformer,true);
		try{
			inst.retransformClasses(c);
		}catch(UnmodifiableClassExceptione){
			e.printStackTrace();
		}finally{
			inst.removeTransformer(transformer);
		}
	}
}

5、编写MANIFEST.MF文件。构建Jar包。

Manifest-Version:1.0
Can-Redefine-Classes:true
Can-Retransform-Classes:true
Premain-Class:top.javap.agent.MyAgentClass

6、编写测试类。利用JDK动态代理生成代理类。然后将代理类的Class文件导出。

publicclassAgentDemo{
	publicstaticvoidmain(String[]args)throwsException{
		Objectinstance=Proxy.newProxyInstance(A.class.getClassLoader(),newClass[]{A.class},newInvocationHandler(){
			@Override
			publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
				returnnull;
			}
		});

		ClassUtil.classDump(instance.getClass(),
				"/target/X.class");
	}

	publicstaticinterfaceA{
		voida();
	}
}

7、设置-javaagent参数并启动程序。

java-javaagent:agent.jarAgentDemo

此时。target目录下就会生成X.class文件。通过IDEA打开即可看到JDK生成的代理类源码。

5. 总结

JavaAgent十分强大。通过它可以在JVM加载Class文件前修改字节码。甚至修改JVM已经加载的Class。基于此。我们可以「零侵入」的对应用程序做增强。服务实现热部署等等。

本文通过一个小示例。编写ClassFileTransformer实现类导出对象的Class文件。反编译查看其源码。这对于ASM操作字节码、JDK动态代理等动态生成类的场景下。而我们又想看对象的具体实现时。提供了帮助。

以上就是由优质生活领域创作者 生活常识网 整理编辑的,如果觉得有帮助欢迎收藏转发~

分享到 :
相关推荐

保卫萝卜3公园34攻略(《保卫萝卜3》)

请用语音读文章一、游戏开始后首先暂停。在怪物出口放一个毒针。升级到中级。一个毒针完[...

描写春天的现代诗简短(「现代诗」春之序曲)

请用语音读文章初春。美丽又娇娇到处都是春的味道一丝丝温暖的风几声雷响炮把大[&hel...

京东快递投诉电话是多少(怎么投诉京东快递)

请用语音读文章京东商城购物的过程中。可能会遇到一些问题。大家第一时间是联系客服寻求[...

2021电动轿车十大品牌排行榜(自主品牌占半)

请用语音读文章Hello各位好。欢迎接到小蚊子聊车。今天为各位盘点一下2021年6[...

发表评论

您的电子邮箱地址不会被公开。