java项目源码哪里找(java初学者练手项目)

1.前言为什么会接触JavaAgent呢?这起源于笔者最近在读Dubbo的源码,Dubbo有一个很有意思的功能——SPI,它可以根据运行时的URI参数,自适应的调用特定的实现类。大致的原理其实也能猜

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()。

public class MyAgentClass {

// JVM优先执行该方法
public static void premain(String agentArgs, Instrumentation inst) {
System.err.println(\"main before...\");
}

public static void premain(String agentArgs) {
System.err.println(\"main before...\");
}
}

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.jar JavaApp

主程序运行后Agent的步骤:

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

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

public class MyAgentClass {

public static void agentmain(String agentArgs, Instrumentation inst) {
System.err.println(\"main after...\");
}
}

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进程。

public static void main(String[] args) throws Exception {
VirtualMachine vm = VirtualMachine.attach(\"8080\");
vm.loadAgent(\"/dev/agent.jar\");
}

3. 相关组件

3.1 Instrumentation

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

public interface Instrumentation {
/**
* 添加Class文件转换器,底层采用数组存储
* JVM加载Class文件前,需要依次经过转换
* @param transformer
* @param canRetransform 是否允许转换
*/
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
void addTransformer(ClassFileTransformer transformer);

// 删除Class文件转换器
boolean removeTransformer(ClassFileTransformer transformer);
boolean isRetransformClassesSupported();

// 重新转换Class
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
boolean isRedefineClassesSupported();

// 重新定义Class,热更新
void redefineClasses(ClassDefinition... definitions)
throws ClassNotFoundException, UnmodifiableClassException;
boolean isModifiableClass(Class<?> theClass);
@SuppressWarnings(\"rawtypes\")
Class[] getAllLoadedClasses();
@SuppressWarnings(\"rawtypes\")
Class[] getInitiatedClasses(ClassLoader loader);
// 获取对象大小
long getObjectSize(Object objectToSize);
void appendToBootstrapClassLoaderSearch(JarFile jarfile);
void appendToSystemClassLoaderSearch(JarFile jarfile);
boolean isNativeMethodPrefixSupported();
void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix);
}

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

3.2 ClassFileTransformer

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

public interface ClassFileTransformer{

/**
* 转换Class
* @param loader 类加载器
* @param className 类名
* @param classBeingRedefined 原始Class
* @param ProtectionDomain
* @param classfileBuffer Class文件字节数组
*/
byte[] transform( ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException;
}

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

4. 实战

【需求】

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

【实现】

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

public class InstrumentationHolder {
private static Instrumentation INSTANCE;

public static void init(Instrumentation ins) {
INSTANCE = ins;
}

public static Instrumentation get() {
if (INSTANCE == null) {
throw new RuntimeException(\"检查 -javaagent 配置\");
}
return INSTANCE;
}
}

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

public class MyAgentClass {

public static void premain(String agentArgs, Instrumentation inst) {
System.err.println(\"main before...\");
InstrumentationHolder.init(inst);
}
}

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

public class ClassDumpTransformer implements ClassFileTransformer {
private final File file;
private final Set<Class<?>> classes = new HashSet<>();

public ClassDumpTransformer(String path, Class<?>... classes) {
this.file = new File(path);
this.classes.addAll(Arrays.asList(classes));
}

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (classes.contains(classBeingRedefined)) {
FileUtil.writeBytes(classfileBuffer, file);
}
return null;
}
}

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

public class ClassUtil {

public static void classDump(Class<?> c, String path) {
ClassDumpTransformer transformer = new ClassDumpTransformer(path, c);
Instrumentation inst = InstrumentationHolder.get();
inst.addTransformer(transformer, true);
try {
inst.retransformClasses(c);
} catch (UnmodifiableClassException e) {
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文件导出。

public class AgentDemo {
public static void main(String[] args) throws Exception {
Object instance = Proxy.newProxyInstance(A.class.getClassLoader(), new Class[]{A.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});

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

public static interface A {
void a();
}
}

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

java -javaagent:agent.jar AgentDemo

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

5. 总结

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请发送邮件至 55@qq.com 举报,一经查实,本站将立刻删除。转转请注明出处:https://www.szhjjp.com/n/83572.html

(0)
nan
上一篇 2022-01-13
下一篇 2022-01-13

相关推荐

  • 养老公寓多少钱一个月(养老院多少钱)

    编者按养老院的生活是怎样的?未来养老将是什么样子?什么时候可以开始养老规划?前一段时间,“第一批90后已经去考察养老院”的新闻冲上热搜。随着我国人口老龄化趋势不断加剧,怎样才能体面地安享晚年,成为市民的热点话题。正值2021年重阳节,南国早报推出特别…

    2022-01-15 随笔
    0
  • linux下压缩文件夹命令(linuxzip命令详解)

    在Linux上有不少用于压缩文件的命令。最新最有效的一个方法是xz,但是所有的方法都有节省磁盘空间和维护备份文件供以后使用的优点。在这篇文章中,我们将比较这些压缩命令并指出显著的不同。tartar命令不是专门的压缩命令。它通常用于将多个文

    2021-09-26
    0
  • 中国学历最高级别解释(最高学历层次是什么)

    据教育部网站消息,9月29日,教育部针对网友提问“专升本(非成人高招专升本)毕业后的第一学历是专科还是本科”进行答复。答复称:学历是指人们在教育机构中接受科学文化教育和技能训练的学习经历,国家教育行政部门相关政策及文件中没有使用“第

    2021-12-07
    0
  • 邮政信用卡怎么申请(我要办邮政银行信用卡)

    邮政信用卡怎么申请?我要办邮政银行信用卡,爱惜日带你了解相关信息。经常看到有朋友说邮储银行信用卡申请有点难,虽然邮储银行的信用卡发卡量并不大,但是因为它分布范围广,覆盖区域多,还是有很多朋友想要申请邮储银行的信用卡,那么邮储信用卡的申请条件是怎样的呢?我爱卡本期就来讲一讲这个问题。首先,申请人需要满足申请信用卡的基本条件1、年龄,申请人年龄在1860周岁之间,具有完全民事行为能

    2021-08-14
    0
  • 随身wifi一年交多少钱(随身wifi是怎么收费的)

    随身wifi一年交多少钱?随身wifi是怎么收费的,久久派带你了解相关信息。如今哪种上网模式最便宜、稳定、方便,不用说,肯定是随身wifi,随着人们对流量的需求,随身wifi便成为一种趋势,然而,随身wifi的价格一直是大家关注的焦点,今天我们就来了解不同的随身wifi需要什么?随身wifi套餐价格表2021,随身wifi一年费用多少钱一、适合人群频繁旅行、往返各地、土木系、司

    2021-12-27
    0
  • 鲲鹏吃龙的视频(鲲鹏为什么吃龙)

    传说中吃龙的上古神兽一直是网友们热议的焦点之一,那么传说中吃龙的上古神兽是谁呢?赶快一起来看看吧!传说中能吃龙的上古神兽有两个记载,其中一个是世间所有神兽、灵兽、恶兽的始祖,一个是孔雀大明王的同胞。传…

    2021-12-01
    0

发表回复

登录后才能评论