目录

IntelliJ IDEA插件笔记

字节码

ASM简单入门

1.ASM简介

动态修改Java的class字节码的框架

官网:https://asm.ow2.io/

1.1 ASM的作用
  • 注解+注入:假设有一个判断登录功能,可以使用注解,在方法开始的地方,解析注解,然后插入代码
  • 闭源代码修改:假设有一个闭源的代码内部有bug,可以通过修改字节码的方式来进行修改
  • 统计功能:Android中可以在方法开始和方法结束,插入代码桩,来得出时间,找出anr
1.2 ASM简单使用

使用idea新建工程,首先导入asm:

1
2
    implementation("org.ow2.asm:asm:9.2")
    implementation("org.ow2.asm:asm-commons:9.2")

编写简单的测试类:

ASM01.java

1
2
3
4
5
6
package com.ifreedomer.asm;
public class ASM01 {
    public static void main(String[] args) {
        System.out.println("hello asm");
    }
}

IDEA使用插件 asm outline 查看 asm 如何生成该类。

安装插件: asm-bytecode-outline

然后双击shift->show bytecode outline,得到如下字节码:

./19.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// class version 52.0 (52)
// access flags 0x21
public class com/ifreedomer/asm/ASM01 {

  // compiled from: ASM01.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 2 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/ifreedomer/asm/ASM01; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 4 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "hello asm"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 5 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1
}

我们首先看一下最简单的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.ifreedomer.asm

import org.objectweb.asm.*
import java.io.FileInputStream
import org.objectweb.asm.commons.AdviceAdapter
import org.objectweb.asm.commons.Method

fun main(args: Array<String>) {
    //读取class
    val inputStream = FileInputStream("/Users/xx/Documents/Study/ASMStudy/build/classes/java/main/ASM01.class")
    //class解析类
    val classReader = ClassReader(inputStream)
    //class写出类
    val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
    //class访问类,所有类的信息都可以在这里面获取到
    val classVisitor = AMSClassVisitor(Opcodes.ASM9, classWriter)
    classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
}

//记录methodName
var methodName = ""
class AMSClassVisitor(api: Int, classVisitor: ClassVisitor) : ClassVisitor(api, classVisitor) {
    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
        return ASMMethodVisitor(
            api = api,
            name = name,
            methodVisitor = methodVisitor,
            access = access,
            descriptor = descriptor
        )
    }
}

/**
 * @param api asm的版本,@link{Opcodes.ASM9}
 * @param methodVisitor 方法访问器
 * @param access 访问作用于 public protect等
 * @param name 方法名
 * @param descriptor 类方法的签名描述
 */
class ASMMethodVisitor(
    api: Int, methodVisitor: MethodVisitor?, access: Int, name: String?,
    descriptor: String?
) : AdviceAdapter(
    api,
    methodVisitor, access, name, descriptor
) {
    //方法进入
    override fun onMethodEnter() {
        System.out.println("onMethodEnter $methodName")
    }

    //方法退出
    override fun onMethodExit(opcode: Int) {
        System.out.println("onMethodExit $methodName")
    }

    init {
        if (name != null) {
            methodName = name
        }
        println("name = $name  desc = $descriptor")
    }
}

这是ASM最简单的代码了,首先看main方法:

  • 读入class
  • 构造ClassReader
  • 构造ClassWriter
  • 构造ClassVisitor
  • 替换ClassVisitor内部的visitMothod,换成我们自己的ASMMethodVisitor

看看打印:

1
2
3
4
5
6
name = <init>  desc = ()V
onMethodEnter <init>
onMethodExit <init>
name = main  desc = ([Ljava/lang/String;)V
onMethodEnter main
onMethodExit main

对比字节码,我们发现,ASM01.class有两个方法,分别是init()和main(),所以我们的的程序正常work,这是ASM最简单的入门

jvm 字节码jclasslib解读

我们在idea里面用jclasslib插件对一个简单的代码实例进行分析。

源代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class test01 {
    private int num=0;
 
    public static void main(String[] args) {
        test01 t=new test01();
        int num=10;
        t.test01();
    }
    public void test01(){
        String name="hj";
        System.out.println(name);
    }
 
}

jclasslib里面如下所示:

./20.png

main方法签名:名字,描述符放在常量池里面,都是字面量

./21.png

接下来分析它的code属性!也是最复杂的一项

./22.png

字节码如上,都是一些操作栈指令,可见长度为16

./23.png

里面LineNumberTable记录了操作指令和源代码直接的对应关系

./24.png

LocalVariableTable 记录了局部变量表信息,可以看到有三项,分别是args、t、num,其中序号对应的是slot槽的对应索引,long,double占两个slot,序号对应它的第一个slot的索引

其中还可以看出它们的起始作用域,例如t:8-15,num:11-15

接下来看一下非static方法的局部变量表:

./25.png

局部变量表的长度是2,TIP非static方法,初始化的时候this属性会被分配,并记录在索引为0的位置,这是约定俗成的,便于虚拟机加载。

热部署

JRebel插件使用详解

简介

JRebel是一套JavaEE开发工具。 Jrebel 可快速实现热部署,节省了大量重启时间,提高了个人开发效率。 JRebel是一款JAVA虚拟机插件,它使得JAVA程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。JRebel使你能即时分别看到代码、类和资源的变化,你可以一个个地上传而不是一次性全部部署。当程序员在开发环境中对任何一个类或者资源作出修改的时候,这个变化会直接反应在部署好的应用程序上,从而跳过了构建和部署的过程,可以省去大量的部署用的时间。

JRebel是一款JVM插件,它使得Java代码修改后不用重启系统,立即生效。 IDEA上原生是不支持热部署的,一般更新了 Java 文件后要手动重启 Tomcat 服务器,才能生效,浪费时间浪费生命。 目前对于idea热部署最好的解决方案就是安装JRebel插件。

安装

第一步:安装插件
在线安装

./10.png

或离线安装

(下载离线包)

也可以自行到官网下载

第二步:在线GUID地址:在线生成GUID

网址:在线GUID地址

./11.png

如果失效刷新GUID替换就可以!

服务器地址:https://jrebel.qekang.com/{GUID}

第三步:打开jrebel 如下所示面板,选择Connect to online licensing service
在线激活

./12.png

可选使用反代工具github:ilanyu

离线激活

下载激活java工程

使用已经封装好的jar包,保持一直运行即可(放到服务器上)。   链接;密码: dscu  码云地址 服务器上使用如下命令启动:

1
java -jar JrebelBrainsLicenseServerforJava-1.0-SNAPSHOT-jar-with-dependencies.jar -p 1008 &
  • -p:可修改端口,默认8888

激活地址

1
http://127.0.0.1:8888/{{UUID}}

与在线类似,注意端口占用

安装成功之后就可以通过JRebel启动项目。这样修改完Java代码后,就可以通过快捷键 Ctrl+shift+F9 而不再需要重启站点这样繁琐浪费时间的操作了。

相关设置

设置成离线工作模式

./13.png

./14.png

设置自动编译

要想实现热部署,首先需要对Intellij按如下进行设置:

  1. 由于JRebel是实时监控class文件的变化来实现热部署的,所以在idea环境下需要打开自动变异功能才能实现随时修改,随时生效。

    ./15.png

  2. 打开运行时编译

设置compiler.automake.allow.when.app.running

快捷键ctrl+shift+A,搜索:registry 或者 按快捷键 Ctrl+Shift+Alt+/ ,选择 Registry

./16.png

./17.png

使用

运行项目时要点击图中红框中的按钮,即可运行:

./18.png

第一个按钮是Run,第二个按钮是Debug。

修改代码(只测试了Java代码的修改)后,按快捷键 Ctrl + Shift + F9,运行后会提示有变化是否重新加载,选yes。完成加载以后,就已经实现了热更新效果。

新版本找不到这个选项看这

统计代码行数插件

Statistic

安装步骤

  1. 第一步首先需要知道idea统计项目代码行数,主要是使用Statistic插件来统计,点击File->Settings,如下图所示:

    ./1.jpg

  2. 第二步进去Settings界面之后,点击Plugins,然后点击下方正中间的Browse repositories,如下图所示:

    ./2.jpg

  3. 第三步进去插件应用之后,搜索Statistic,选中之后,点击右侧的Install进行安装插件,如下图所示:

    ./3.jpg

  4. 第四步等待几秒钟,插件就安装成功了,点击close,如下图所示:

    ./4.jpg

  5. 第五步返回上一界面,可以看到Statistic插件,点击OK之后,会要求重启idea,如下图所示:

    ./5.jpg

  6. 第六步重启idea之后,在左下方可以看到Statistic,点击,可以看到项目代码的行数,如下图所示:

./6.jpg

详解

​ 1)名称解释:

​ Refresh 对当前项目代码统计

​ Refresh on selection 对当前打开文件的代码统计

​ Setting 配置项

​ 2)Overview 全局统计

./7.png

​ Extension 按扩展名分类

​ Count 文件数

​ Size SUM 文件大小,单位KB

​ Size MIN 最小文件大小

​ Size MAX 最大文件大小

​ Size AVG 平均文件大小

​ Lines 代码行数(包含注释和空行)

​ Lines MIN 最小行数

​ Lines MAX 最大行数

​ Lines AVG 平均行数

​ Lines CODE 代码行数(不包含注释和空行)

​ 3)指定的文件,比如java/xml等

./8.png

​ Source File 文件名称

​ Total Lines 总行数(包含注释和空行)

​ Source Code Lines 源代码行数(不包含注释和空行)

​ Source Code Lines[%] 源代码占比

​ Comment Lines 注释行数

​ Comment Lines[%] 空行占比

​ Blank Lines 空行数

​ Blank Lines[%] 空行占比

​ 4)Settings

​ 纳入统计范围的文件,按后缀区分。

./9.png