字节码编程|工作多年的你是否接触过这种技术?

简介: 字节码编程|工作多年的你是否接触过这种技术?

大家好,我是冰河~~

最近和不少小伙伴聊天,发现大部分小伙伴,其中可能就包括正在看文章的你和我,工作时间已经不短了,有些小伙伴工作3~5年了,有些甚至超过8年了。

但是大部分小伙伴平时的工作都是在简单的做着CRUD,疲于应付日常工作中的业务开发和修复系统Bug,每天都会加班到很晚。根本没时间去提升自己的技术能力,久而久之,自己的技术能力和工作年限出现了严重的不匹配现象。

题外话

其实,针对这些情况,有不少小伙伴曾不只一次的问过我:冰河,我感觉我的职业生涯很迷茫,能给我点建议吗?我工作时间不短了,整天做一些CRUD的工作,根本没时间学习啊!

说实话,在互联网这个行业里,每个人或多或少的都会有过这种迷茫的阶段吧,我也有过。只不过我会让这种迷茫的感觉瞬间消失,更过的是去思考究竟是什么让我产生了这种迷茫的感觉?是技术能力?是业务理解?是人际交往?是职场规则?等等。。。

image.png

我会把这些可能导致我迷茫的因素结合自身的实际情况进行深度分析:

  • 如果是技术能力不足导致我产生了迷茫感,那我就会抓住一切可利用的业余时间提升自身技术能力。
  • 如果是对公司的业务理解不足导致的迷茫感,那我就会加强对业务的理解,不断熟悉、推演、反复论证。
  • 如果是人际交往存在问题导致的迷茫感,那我就会加强对于人际关系的处理能力。
  • 如果是不熟悉职场规则导致的迷茫感,那我就会加强个人的职场素养。
  • 消灭导致迷茫感的其他因素。

所以,小伙伴们产生迷茫感并不重要,重要的是要分析出让你产生迷茫感的因素有哪些,是外在因素还是内在因素。找到让你产生迷茫感的因素之后,再将这些问题逐渐分解,慢慢提升自己缺乏的某种技能。这个过程或许比较漫长,可能需要几天,几个星期或者几个月的时间,这就需要各位小伙伴们要踏下心来慢慢沉淀和积累了。

冰河送给大家一句话:持之以恒,贵在坚持,每天进步一点点。

说了这么多,算是对正在处于迷茫期的小伙伴们的一点小小的建议吧。

好了,为了帮助小伙伴们尽快的度过迷茫期,冰河希望能够在技术上更多的帮助到大家,从今天开始,为大家分享一些关于字节码编程的知识,这或许是你一直从事CRUD工作根本没有接触过的。

动态字节码技术

了解Java的小伙伴都知道,我们使用Java编写的代码是需要编译成字节码之后才能在JVM中运行的,而字节码一旦被加载到JVM的内存中,就可以被解释执行了。而Java源代码并编译后往往会生成对应的class文件,其实只要是文件,或多或少的就可以被修改。

如果我们使用某种技术按照某种规则对字节码文件进行了修改,重新定义了字节码的执行逻辑,或者加上我们自己的逻辑,这样不就改变了原有代码的执行逻辑吗?

除了修改原有的字节码之外,我们也可以利用动态字节码技术来动态创建一个新的类,使其完成我们想要的业务逻辑。

动态字节码的优势就是可以不改变之前的源代码,在程序生成字节码后,对生成的字节码进行修改,或者在运行期间动态生成新的类或者方法,可以真正的做到零侵入。

如何实现字节码编程

在Java领域,有很多可以实现动态修改字节码的技术,比较流行的应该有三个:ASM、Javassist和Bute-buddy。

image.png

  • ASM:直接操作字节码的指令,执行的效率比较高,但是要求使用者提前掌握Java字节码文件的格式和指令,对于使用者的要求比较高。
  • Javassist:提供了高级的API,执行的效率和ASM相比,相对要差一些,但是无需了解Java字节码的格式和指令,对于使用者的要求比较低。
  • Bute-buddy:提供了高级的API,执行的效率和ASM相比,相对要差一些,但是无需了解Java字节码的格式和指令,对于使用者的要求比较低。

字节码编程使用场景

试想,某天,你正坐在工位上愉快的敲着Bug,此时你的技术领导让你实现这样一个需求:在程序的运行期间,向某个类的某个方法的前面和后面加入某段业务代码,或者根据具体的业务场景替换掉某个方法的执行逻辑。你的领导又特别对你提醒了一句:注意是在运行期间动态修改,要作者零侵入,不要在源代码的基础上修改。

听到这个需求时,你或者会想到Spring的AOP代理技术,没错,Spring的AOP代理技术确实可以实现这个需求。但是这样做需要在被代理的方法上添加注解,修改了原有的代码,不符合需求。另外,使用Spring的AOP技术的性能会比字节码编程低。

此外,大量的开源框架底层也使用到了字节码编程技术。例如,阿里开源的Dubbo、Arthas等。

字节码编程还有一个非常重要的核心应用场景——APM(应用性能管理)的实现。后面冰河会带着大家手撸一个完整的可使用的APM系统。

入门案例

开发环境

  • JDK 1.8
  • IDEA 2018.03

完整代码

冰河已将本文章的完整案例代码提交到了GitHub和Gitee,目前正在已案例的形式持续更新,后面会基于字节码编程实现一个可用的APM系统。

GitHub:https://github.com/sunshinelyz/bytecode

Gitee:https://gitee.com/binghe001/bytecode

本文对应的案例代码为:bytecode-javassist-01。如果文章对你有点帮助,小伙伴们在GitHub和Gitee上点个Star呀~~

案例效果

在main()方法运行之前运行premain()方法。

动手实践

这个入门案例,我们先使用Javassist实现。创建Maven工程 bytecode-javassist-01, 在pom.xml文件中引入Javassist相关的依赖。

<properties>
    <javassist.version>3.20.0-GA</javassist.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>${javassist.version}</version>
    </dependency>
</dependencies>

添加项目构建模块,指定项目的Premain-Class

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Project-name>${project.name}</Project-name>
                            <Project-version>${project.version}</Project-version>
                            <Premain-Class>io.binghe.bytecode.javassist.test.Premain</Premain-Class>
                            <Boot-Class-Path>javassist-3.20.0-GA.jar</Boot-Class-Path>
                            <Can-Redefine-Classes>false</Can-Redefine-Classes>
                        </manifestEntries>
                    </archive>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>

创建Main类。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description Javassist的第一个测试程序
 */
public class Main {
    public static void main(String[] args){
        System.out.println("hello main");
    }
}

创建Premain类。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 第一个pemain程序,在main方法前面执行
 */
public class Premain {
    public static void premain(String args){
        System.out.println("hello premain");
    }
}

看到这里,相信不少小伙伴会明确我们的最终效果了吧,没错,就是运行程序时,先输出hello premain 后输出 hello main

可能又会有小伙伴会问:程序的启动入口不就是main()方法吗?既然main()方法是程序的入口,那为啥不先执行main()方法呢?

别急,我们先来看下最终的效果,至于内部的原理,我们会在【字节码编程】专栏后面的文章中详细介绍。

第一次运行程序

这里,我们还是要运行main()方法,控制台输出的效果如下所示。

image.png

我去,啥情况,不是说了要先输出hello premain 后输出 hello main 吗?为啥只输出了 hello main ?难道是翻车了?

其实,这里是需要对程序进行简单的配置。

配置程序

首先,在IDEA中配置好Maven,将 bytecode-javassist-01项目打包成Jar文件,将打包好的 bytecode-javassist-01-1.0.0-SNAPSHOT.jar 文件拷贝到D盘根目录(可以拷贝到任意目录或者不拷贝都行)。

然后在IDEA中配置下main()方法的启动参数,在IDEA的Program arguments中输入如下配置。

-javaagent:D:\bytecode-javassist-01-1.0.0-SNAPSHOT.jar

image.png

点击 Apply,然后点击 OK。完成配置。

第二次运行程序image.png

看到没,小伙伴们,确实是先输出了hello premain 后输出了 hello main

是不是很神奇,在接下来的一段时间内,我们开启一段神奇的字节码编程之旅吧。

总结

作为【字节码编程】的开篇,在文章的开始,就很多小伙伴迷茫的点,冰河给出了一些简单的建议,希望能够给正处于迷茫期的小伙伴们一点帮助。

接下来,我们介绍了动态字节码技术、如何实现字节码编程和字节码编程的使用场景。最后我们通过一个小案例让小伙伴们认识到尽管main()方法是整个程序的入口,但是在main()方法运行前,还是可以运行其他方法的。

最后,告诉大家一个好消息,经过很长时间的努力,冰河的个人博客也快上线啦,期待ing~~

好了,今天就到这儿吧,我是冰河,我们下期见~~

相关实践学习
通过轻量消息队列(原MNS)主题HTTP订阅+ARMS实现自定义数据多渠道告警
本场景将自定义告警信息同时分发至多个通知渠道的需求,例如短信、电子邮件及钉钉群组等。通过采用轻量消息队列(原 MNS)的主题模型的HTTP订阅方式,并结合应用实时监控服务提供的自定义集成能力,使得您能够以简便的配置方式实现上述多渠道同步通知的功能。
相关文章
|
2月前
|
Arthas 存储 运维
记Arthas实现一次CPU排查与代码热更新
本文介绍如何使用Arthas排查线上Java应用CPU占用过高问题。通过`thread`定位高耗能线程,`watch`监控方法调用与异常,结合`jad`反编译与`redefine`实现热更新,无需重启服务即可修复代码,提升线上问题响应效率。适用于SpringBoot + JDK8环境。
|
Kubernetes 监控 Java
【JVM故障问题排查心得】「内存诊断系列」Docker容器经常被kill掉,k8s中该节点的pod也被驱赶,怎么分析?
【JVM故障问题排查心得】「内存诊断系列」Docker容器经常被kill掉,k8s中该节点的pod也被驱赶,怎么分析?
1154 0
【JVM故障问题排查心得】「内存诊断系列」Docker容器经常被kill掉,k8s中该节点的pod也被驱赶,怎么分析?
|
4月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
6月前
|
存储 SQL 关系型数据库
MySQL中binlog、redolog与undolog的不同之处解析
每个都扮演回答回溯与错误修正机构角色: BinLog像历史记载员详细记载每件大大小小事件; RedoLog则像紧急救援队伍遇见突發情況追踪最后活动轨迹尽力补救; UndoLog就类似时间机器可倒带历史让一切归位原始样貌同时兼具平行宇宙观察能让多人同时看见各自期望看见历程而互不干扰.
327 9
|
消息中间件 NoSQL Java
Redis Streams在Spring Boot中的应用:构建可靠的消息队列解决方案【redis实战 二】
Redis Streams在Spring Boot中的应用:构建可靠的消息队列解决方案【redis实战 二】
8775 1
|
SQL 存储 关系型数据库
MySQL创建数据表(CREATE TABLE语句)
MySQL创建数据表(CREATE TABLE语句)
1968 0
|
监控 Cloud Native Java
字节码编程,Byte-buddy篇二《监控方法执行耗时动态获取出入参类型和值》
在前面的ASM、Javassist 章节中也有陆续实现过获取方法的出入参信息,但实现的方式还是偏向于字节码控制,尤其ASM,更是需要使用到字节码指令将入参信息压栈操作保存到局部变量用于输出,在这个过程中需要深入了解Java虚拟机规范,否则很不好完成这一项的开发。但!ASM也是性能最牛的。其他的字节码编程框架都是基于它所开发的。
938 0
字节码编程,Byte-buddy篇二《监控方法执行耗时动态获取出入参类型和值》
|
XML Java Maven
Springboot Starter 是如何工作的?
Springboot Starter 是 Springboot 项目的一部分,简化了依赖管理和自动配置,通过 Maven 或 Gradle 引入相关依赖并自动配置应用程序。其核心特性包括依赖管理、自动配置及条件注解。Starter 的设计思维体现了模块化、约定优于配置、依赖注入等原则,提高了开发效率,但也存在调试复杂、过度依赖等问题。
618 3
|
存储 缓存 自然语言处理
(三)JVM成神路之全面详解执行引擎子系统、JIT即时编译原理与分派实现
执行引擎子系统是JVM的重要组成部分之一,在JVM系列的开篇曾提到:JVM是一个架构在平台上的平台,虚拟机是一个相似于“物理机”的概念,与物理机一样,都具备代码执行的能力。
427 1
|
Java Python
如何通过Java程序调用python脚本
如何通过Java程序调用python脚本
804 0