基于自定义类加载器的在线代码执行器(线上编译执行)

目录

一、引言

二、原理

1、原理概述

2、原理详解

三、方法的特点

四、对线上的影响(内存和性能)

五、代码实现(服务端部分)

六、具体使用(客户端使用)

1、引入jar

2、引入RequestContextListener

3、新增Controller(远程方法执行接口)

4、具体调用

七、惯例


一、引言

在我们日常工作中总会遇到如下问题,那咱们平时都是怎么处理的呢(先想想)?

  1. 业务出错,怀疑上游接口问题,但是线上又没有打印接口返回数据怎么办?

  2. 业务出错,怀疑代码业务逻辑有问题,需要代码逻辑环节输出相应参数定位怎么办?

  3. 线上有业务数据异常,需要直接数据库操作修复怎么办?

  4. 上游接口偶尔异常,但是由于没有日志,没有证据怎么办?

  5. 需要对上游接口做各种场景测试怎么办?

  6. 想要执行线上某段业务代码逻辑怎么办?

针对上述问题,我们通常有如下常规解决办法:

对于只能在线上环境(生产环境)定位的问题:

  1. 新增相应的info日志,然后打包、上线,查看日志。

  2. 新增调试代码逻辑,通过controller调用,然后打包、上线,直接调用controller得到需要的结果。

可以在预发环境(测试床)重现或者定位的问题:

  1. 新增相应debug日志,然后打包,发布到预发环境,查看日志。

  2. 新增对应代码逻辑,通过controller调用,然后打包、发布到预发环境,直接调用controller得到需要的结果。

无论怎么处理,最终都需要重新打包、发布,甚至是上线。整个过程不复杂,但是很繁琐。那么有没有一种方法可以不打包,不上线,不发布,不重启就能够在线上直接执行某段代码,或者打印相应的日志呢?

答案是有,接下来本文将介绍一种基于”自定义类加载器“的在线执行方法实践。通过该方法,我们就可以在不打包,不上线,不发布,不重启的前提下,在线上直接执行某些代码逻辑,或者打印任何需要的日志

二、原理

1、原理概述

主要原理是参考了”深入理解Java虚拟机“类加载器章节,提到的实现,具体内容请查阅书籍。

首先在本地工程编写你需要的代码(一个完整的包含main函数的类)逻辑,所有日志通过System.out的方式输出,然后通过接口将源码直接提交到线上编译,并执行(反射执行main函数),最终返回所有System.out的所有输出结果。该方式的特点如下:

  1. 代码可以直接在线上执行:则可以执行任何已有代码,编写任何调试逻辑。

  2. 所有System.out的输出结果都会返回:这样就可以打印任何需要的数据(即日志)。

2、原理详解

其大致原理图和流程图如下:

  1. 先编写源码,源码可以引用任意线上实例。需要输出(返回)的内容使用System.out.println打印

  2. 将源码提交到线上

  3. 通过JavaCompiler对源码进行编译,得到编译后的字节码

  4. 将字节码中字符串常量池中的java/lang/System,修改为自定义的IntectionSystem。目的是修改System.out.print的输出行为。让其所有输出输出到一个PrintBuffer中,这样执行完后就能够将得到所有的输出内容。

  5. 使用自定义类加载器加载修改后的类字节码,得到Class类。然后初始化出对应的实例。

  6. 通过反射调用实例的main函数。

  7. 执行结果(全部在PrintBuffer中)返回给客户端。这样客户端就可以得到执行结果了。

三、方法的特点

方便、快捷。

方便体现在:可以编译任意代码,这样可以实现任何所需逻辑。

快捷体现在:无需打包、发布、上线和重启等繁琐的步骤,直接提交就可知结果。

四、对线上的影响(内存和性能)

对线上几乎没有影响,因为所有代码的编译是由自定义类加载器(一个局部变量)完成的,当调用完成之后,自定义类加载器就会被回收,那么对应的实例和类都会被回收。

所以不会对线上的内存和性能造成影响。

五、代码实现(服务端部分)

上述核心流程(服务端部分逻辑 )的具体实现已经上传到github。具体请访问:https://github.com/lifeofcoder/dynamic-executor

六、具体使用(客户端使用)

1、引入jar

首先下载github上的源码,直接mvn编译即可得到jar,然后将jar导入到你的服务端web工程中即可。

2、引入RequestContextListener

在Web.xml中引入RequestContextListener,目标是让我们在自定义代码(非Spring管理对象)中能够获取到ApplicationContext,这样我们才能够获取到Spring管理的所有Bean对象。如果代码不需要获取Spring的Bean,则可以不引入。如果工程本身有其他的方式可以在普通java对象(非Spring管理对象)中获取到Spring管理的对象,则完全不用关系这一步骤了。

3、新增Controller(远程方法执行接口)

因为需要给客户端提供一个提交代码和得到结构的Api入口。所有我们需要在Web工程中增加一个Controller,并做好权限控制(是否需要权限控制大家根据实际情况而定)。如下,只是做了一个简单的权限控制,即要求传递一串指定的密文来做身份验证。具体实践的时候可以根据实际情况,做对应的权限控制。

4、具体调用

我们首先编写一个需要执行的类(类必须有main函数),如下举例,我们调用一个服务(Spring管理的类)的方法,并输出执行结果。

将TextExecutor类的完整代码编码,然后作为code参数传递,然后调用执行接口。

通过执行结果可以看到,我们的测试类TextExecutor中的System.out.print输出的内容被直接返回到客户端了。 

七、惯例

如果你对本文有任何疑问或者高见,欢迎添加公众号共同交流探讨(添加公众号可以获得”Java高级架构“上10G的视频和图文资料哦)。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值