源代码在哪里找 在哪能找到Java源码
原创

源代码在哪里找 在哪能找到Java源码

好文

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.0Can-Redefine-Classes:trueCan-Retransform-Classes:truePremain-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.0Can-Redefine-Classes:trueCan-Retransform-Classes:trueAgent-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();//重新转换ClassvoidretransformClasses(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));}@Overridepublicbyte[]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.0Can-Redefine-Classes:trueCan-Retransform-Classes:truePremain-Class:top.javap.agent.MyAgentClass

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

publicclassAgentDemo{publicstaticvoidmain(String[]args)throwsException{Objectinstance=Proxy.newProxyInstance(A.class.getClassLoader(),newClass[]{A.class},newInvocationHandler(){@OverridepublicObjectinvoke(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动态代理等动态生成类的场景下。而我们又想看对象的具体实现时。提供了帮助。

您还感兴趣的文章推荐

以上就是由互联网推广工程师 桔子生活网 整理编辑的,如果觉得有帮助欢迎收藏转发~

分享到 :
相关推荐

回复 一桥孤寂 取消回复

登录... 后才能评论

评论(2)

  • 难入眠 永久VIP 2022年12月14日 00:34:23

    源代码在哪里找 在哪能找到Java源码 这篇解答确实也是太好了

  • 长空夕醉 永久VIP 2022年12月14日 00:34:23

    文件,源码,加载,字节,转换器,主程序,程序,方法,它可以,数组

  • 一桥孤寂 永久VIP 2022年12月14日 00:34:23

    1. 前言为什么会接触javaAgent呢?这起源于笔者最近在读Dubbo的源码。Dubbo有一个很有意思的功能——SP