最近导师公司需要有些帮忙的地方,就去来帮忙,结果甩手一个需求JVM的小钩子程序,要求能对固定类型的固定的方法进行获取其中的参数,打印出来到其他日志的地方,而且不能修改别人的jar包。喵喵喵?什么鬼,我之前只是写JAVAEE的啊,接到需求后一脸懵。不过还好,我们可以google和度娘。

一、确定问题

我们在这里要实现的是一个小钩子程序,而且我们没有任何JVM小钩子的经验,那么根据已有的知识,无非是使用静态的代理模式或者是java jdk的动态代理,或者是使用ASM,cglib等方法来进行动态使用。不过好的是spring,mybatis有成功使用的经验,那么我们无非就是使用的简单粗暴点,肯定是能够成功进行下去的只是是一个时间和人力的问题罢了。公司(创业公司)里的软件研发部门关于java开发的就只有我一个人,一方面维护一个原来完全自己写的平台的代码,一边写着自己的毕业论文,一边研究这个新的问题,准备新的项目。

二、知识准备

因为完全没有任何经验,所以在这里就开始对相关的知识进行搜索。

首先大行其道的可能能实现我们功能的是cglib,然后我们往下挖一层可以是使用ASM,再不然直接使用JDK本身提供的Instrument,越往下挖,可做的东西的范围越广,同时其学习成本越高,封装好的东西越少。具体需要到什么程度要看开发人员本身的能力和时间以及软件开发经验。

我个人的学习顺序是从高向低进行学习,首先是cglib这个东西学习比较其他两个简单而且封装也比较完善,我们此处不多做介绍,这里介绍的是Instrument简单使用的方法。

ASM是java的字节码控制框架可以动态的生成字节码,为什么要动态的生成字节码?我个人理解上是这样的,我们进行软件开发的过程一般都是在进行纵向的开发,以SSM为例,经过springMVC,Spring,Mybatis的粘合,转换为了对持久化数据的操作。那么当我们需要进行横向开发的时候一般是进行静态代理,使用适配器或者装饰器的设计模式就可以完成对应的功能,当我们大批量进行类似操作的时候就需要进行横向的扩展,这种情况下我们就需要一个横向的框架,spring的AOP的概念也就很好的诠释了为什么进行横向扩展。

在JAVA中Instrument最早可以追溯到JDK1.5版本,在这里就提出了java.lang.instrument,这个东西是个好东西,我们可以利用它直接进行对class文件的操作,当然也需要掌握理解一些java类相关的基本概念。这里不在赘述更多东西了,我们开始进行小的demo的实验吧。

三、基础准备

首先我们需要进行基本的内容准备,包括我们实验用的interface和impl这里就简单给出对应的内容吧。

3.1 Pom.xml依赖

<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.23.1-GA</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.5</version></dependency>

这里我们只需要使用javassist进行辅助开发就可以了,common-lang是用来进行一些校验使用的

3.2 测试用类型


public interface TestInterface {void testMethod();void testMethod(String name);void testMethod(int name);void testMethod(float name);void testMethod(String name, String desc);}

这是个接口,接口中进行了一些重载,方便我们进行测试获得数据。


public class TestObject implements TestInterface {@Overridepublic void testMethod() {System.out.println("empty method");System.out.println("empty method end");}@Overridepublic void testMethod(String name) {System.out.println("String method");System.out.println(name);System.out.println("String method end");}public void testMethod1(String name) {System.out.println("String method1");System.out.println("String method2 end");}@Overridepublic void testMethod(int name) {System.out.println("int method");System.out.println("int method end");}@Overridepublic void testMethod(float name) {System.out.println("float method");System.out.println("float method end");}@Overridepublic void testMethod(String name, String desc) {System.out.println("String,String method");System.out.println("String,String method end");}
}

这是一个实现类,这个实现类中我们进行了简单的输出字符串的操作,

public class InsertLog {public static void doLog(String doLog) {System.out.println("this is insert log :" + doLog);}
}

这个是我们的要进行的业务逻辑操作,这里简化为输出数据到控制台

3.3 动态代理简介

这里简单的进行了动态代理的测试,简单介绍一下的动态代理,这里面有些东西我们可以用得到。


import com.xxx.hades.test.TestInterface;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;public class DynamicTestObjectProxy implements InvocationHandler {private TestInterface obj;public DynamicTestObjectProxy(TestInterface object) {this.obj = object;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("--------------------------------------------------------");System.out.println("before do invoke");System.out.println("method :" + method);Parameter[] params = method.getParameters();if (null == params || params.length == 0) {System.out.println("No parameter");System.out.println("--------------------------------------------------------\n\n");return null;}for (int i = 0; i < params.length; i++) {Parameter param = params[i];System.out.println("\t\t|param:" + param.getName());System.out.println("\t\t\t|->type:"+param.getType());System.out.println("\t\t\t|->annotations:"+param.getAnnotations());System.out.println("\t\t\t|->value");}method.invoke(obj, args);System.out.println("after do invoke");System.out.println("--------------------------------------------------------\n\n");return null;}
}

如果需要实现动态代理,那么我们就需要进行一个操作,实现InvocationHandler,有一个构造函数保存我们要调用的内容。

简单的实现测试类如下

public class JDKTestMain {public static void main(String[] args) {TestInterface object = new TestObject();InvocationHandler handler = new DynamicTestObjectProxy(object);TestInterface subject = (TestInterface) Proxy.newProxyInstance(handler.getClass().getClassLoader(), object.getClass().getInterfaces(), handler);System.out.println(subject.getClass().getName());subject.testMethod();subject.testMethod("name");}
}

结果吗?简单看看好了。

好的 至此我们的基本介绍结束了。

四 Instrument简单使用

4.1主要转换类

我们使用Java本身自带的instrumation因此实现接口ClassFileTransformer里面的方法,这里只是简单使用。


import com.xxx.hades.test.TestInterface;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import org.apache.commons.lang3.StringUtils;import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.HashSet;
import java.util.Set;public class Transformer implements ClassFileTransformer {private static Set<String> interFaceList = new HashSet<>();static {interFaceList.add(TestInterface.class.getName());}private boolean isInWatch(CtClass[] classes) {for (int i = 0; i < classes.length; i++) {if (interFaceList.contains(classes[i].getName()))return true;}return false;}private byte[] doTransClass(String className, byte[] classfileBuffer) {try {if (StringUtils.isBlank(className))return null;String currentClassName = className.replaceAll("/", ".");CtClass currentClass = ClassPool.getDefault().get(currentClassName);CtClass[] interfaces = currentClass.getInterfaces();if (!isInWatch(interfaces)) {return null;}//引入需要使用的class对应的包ClassPool.getDefault().importPackage("com.yunqutech.hades.bussiness");CtBehavior[] methods = currentClass.getMethods();for (CtBehavior method : methods) {String methodName = method.getName();if ("testMethod".equals(methodName)) {CtClass[] paramsType = method.getParameterTypes();for (CtClass type : paramsType) {String typeName = type.getName();System.out.println("param type:" + typeName);if ((String.class.getName().replaceAll("/", ".")).equals(typeName)) {System.out.println(" this is correct ");//静态类进行设置编码method.insertAt(0, " InsertLog.doLog($1);");break;}}}//finish method}return currentClass.toBytecode();} catch (Exception ex) {ex.printStackTrace();}return null;}@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {System.out.println("doTransFormClass:" + className);return this.doTransClass(className, classfileBuffer);}
}

4.2预处理类

预处理是要进行勾住对应JVM程序的类,因此在这里我们使用的是这样的

import com.xxx.hades.instrument.doInstrument.Transformer;
import java.lang.instrument.Instrumentation;
public class InstrumentPreMain extends Object {public static void premain(String agentArgs, Instrumentation inst) {System.out.println("instrumentPreMain is calling");inst.addTransformer(new Transformer());}
}

4.3 运行主类

import com.xxx.hades.test.TestObject;public class InstrumentMain {public static void main(String[] args) {TestObject object = new TestObject();object.testMethod("jzs");object.testMethod(1);object.testMethod(1.0f);object.testMethod("jzs", "desc");}
}

4.4打包配置

为了方便简洁,我们就打包到同一个包下了,这时我们打包相关的文件META-INF/MAININFEST.MF写的内容如下

Manifest-Version: 1.0
Main-Class: com.yunqutech.hades.instrument.InstrumentMain
Premain-Class: com.yunqutech.hades.instrument.InstrumentPreMain
Can-Redefine-Classes: true
Boot-Class-Path: javassist.jar

4.5运行截图

好吧,好吧我给你们运行结果

运行的时候的使用的命令是 : java -javaagent:hades.jar -jar .\hades.jar

结果如下:

OK结束

JAVA instrument简单使用相关推荐

  1. java入参为方法_Java命令注入原理结合Java Instrument技术(FreeBuf首发)

    一.前言 命令注入:恶意用户构造恶意请求,对一些执行系统命令的功能点进行构造注入,从而达到执行命令的效果. 二.演示环境搭建 这里采用springboot+swagger搭建一个模拟的web环境:启动 ...

  2. java instrument 初探

    java在1.5引入java.lang.instrument,你可以由此实现一个java agent,通过此agent来修改类的字节码即改变一个类.本文中,会通过java instrument 实现一 ...

  3. java简单通讯录的实现02person类_用java实现简单的小游戏(你一定玩过)

    用java实现简单的小游戏(你一定玩过) 对于java初学者来说,通过一些学习小游戏来对swing学习以及对java基础的学习是一个好的方法,同时也给学习带来了很多的乐趣,接下来就给大家分享一个jav ...

  4. java实现简单窗体小游戏----球球大作战

    java实现简单窗体小游戏----球球大作战 需求分析 1.分析小球的属性: ​ 坐标.大小.颜色.方向.速度 2.抽象类:Ball ​ 设计类:BallMain-创建窗体 ​ BallJPanel- ...

  5. Java JNI简单实现

    Java JNI简单实现 JNI(Java Native Interface)允许了Java和C&C++进行交互?这不折腾人嘛! 一.JNI简述 http://baike.baidu.com/ ...

  6. java实现账号单一ip登录,使用Java实现简单后台访问并获取IP示例

    使用Java实现简单后台访问并获取IP示例 发布时间:2020-10-28 21:57:57 来源:亿速云 阅读:92 作者:Leah 使用Java实现简单后台访问并获取IP示例?针对这个问题,这篇文 ...

  7. 用limit 实现java的简单分页

    https://blog.csdn.net/xinyuezitang/article/details/84324359 用limit 实现java的简单分页 xinyuezitang 2018-11- ...

  8. redis java应用_redis在JAVA的简单应用

    reids是一个高性能的key-value数据库.它存储的value支持各种类型的数据,如String,List,set,hash类型.在此基础上,各种不同方式的排序. 本文不具体争对redis数据库 ...

  9. java实现简单的约瑟夫环问题

    我自己学习数据结构的时候,总希望能找到很简单的入门代码,可总是很难找到,于是就想到能写一些简单的java代码.  在百度百科上面搜索到约瑟夫环的问题时,并没有发现java的简单实现,自己在下面弄也是弄 ...

  10. Java Kafka 简单示例

    Java Kafka 简单示例 简介     Java kafka 简单代码示例 maven依赖配置 <!-- kafka --> <dependency><groupI ...

最新文章

  1. 面试必问一:Java 中 == 和 equals 的区别你知道吗
  2. HTML5 开发APP
  3. 假设以带头结点的循环链表表示队列_关于反转链表,看这一篇就够了!
  4. java抓rtp包_Wireshark抓取RTP包,还原语音
  5. Android 性能优化提示
  6. SQL SERVER 新增表、新增字段、修改字段 判断表是否存在
  7. matlab求定积分
  8. 精美商业计划书PPT模版大合集(共107份,900M)
  9. 访问网络共享找不到网络名的解决方案
  10. DEDE源码分析与学习之二: member文件结构说明
  11. 打印excel html js,前端js打印(导出)excel表格的方法实例
  12. Linux下使用GPG(GnuPG)加密及解密文件
  13. 宇宙环境和演化过程统一建模方法——读《奇点临近》有感
  14. 三角形网格 四方形网格_脱离网格生活使我了解了开放性
  15. 伽马校正笔记(Gamma Correction)
  16. jQuery酷炫网格相片墙动画效果
  17. linux查看docker日志,linux:有效使用docker logs查看日志
  18. 关于凡哥npm的小结,感谢凡哥
  19. 【小知识】12个月份用英语表示
  20. Spider学习笔记(十二):视频下载插件ffmpeg的使用操作介绍

热门文章

  1. 为什么DMA方式的优先级高于程序中断方式
  2. php杨辉三角的规律,杨辉三角的规律以及定理
  3. 操作系统:覆盖技术与交换技术
  4. 谈谈新加坡的教育和学区房
  5. 灵遁者组诗:无数个存在的可能
  6. vfp中写入文本文件_Visual FoxPro基础知识
  7. 无人驾驶之KITTI数据集介绍与应用(一)——数据组织方式介绍
  8. Windows10系统 定时开/关机设置
  9. python编程 从入门到实践 第五章 if语句
  10. 《专业创新实践Ⅱ》大作业 LeNet在眼疾识别数据集iChallenge-PM上的应用