Loading...
墨滴

冰河

2021/11/22  阅读:30  主题:姹紫

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

大家好,我是冰河~~

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

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

题外话

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

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

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

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

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

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

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

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

动态字节码技术

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

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

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

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

如何实现字节码编程

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

  • 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()方法,控制台输出的效果如下所示。

我去,啥情况,不是说了要先输出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

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

第二次运行程序

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

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

总结

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

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

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

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

冰河

2021/11/22  阅读:30  主题:姹紫

作者介绍

冰河