详解Java字节码编程之非常好用的javassist

一、Javassist入门

(一)Javassist是什么

Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件。Javassist使用户不必关心字节码相关的规范也是可以编辑类文件的。

(二)Javassist核心API

在Javassist中每个需要编辑的class都对应一个CtCLass实例,CtClass的含义是编译时的类(compile time class),这些类会存储在Class Pool中(Class poll是一个存储CtClass对象的容器)。
CtClass中的CtField和CtMethod分别对应Java中的字段和方法。通过CtClass对象即可对类新增字段和修改方法等操作了。

(三)简单示例

为了减少演示的复杂度,示例以及之后的操作,都在Maven项目下进行,因为我们可以直接引入依赖就可以达到我们导包的目的,很方便,不用再去下载jar包,然后自己手动导入了。

1、创建一个maven项目

如果你使用的是IDEA,可以像我一样;如果是其他工具,可以自行百度,或者按照自己的经验来创建即可。

2、创建一个测试类,代码如下:

package com.ssdmbbl.javassist;

import javassist.*;

import java.io.IOException;
/**
 * @author zhenghui
 * @description: Javassist使用演示测试
 * @date 2021/4/6 6:38 下午
 */
public class JavassistTest {
    public static void main(String[] args) throws CannotCompileException, IOException {
        ClassPool cp = ClassPool.getDefault();
        CtClass ctClass = cp.makeClass("com.ssdmbbl.javassist.Hello");
        ctClass.writeFile("./");
    }
}

当运行这个代码的时候,可以看到已经在项目的根目录下创建了一个“com.ssdmbbl.javassist”包,在这个包下创建了“Hello.java”的java文件。

内容如下:

package com.ssdmbbl.javassist;

public class Hello {
    public Hello() {
    }
}

二、Javassist操作字节码示例

回想一下,咱们如果对一个Java正常操作的话,大概存在哪些操作呢?

  • 1、咱们会对一个类添加字段;
  • 2、咱们会对一个类添加方法;

好像没其他的了吧。其余的就是在方法里写代码了呗。

(一)新增一个方法

咱们继续套用上面简单示例的代码,在此基础之上进行新增一个方法。

新增方法的名字为"hello1",传递两个参数分别为int和double类型,并且没有返回值。

package com.ssdmbbl.javassist;

import javassist.*;

import java.io.IOException;
import java.net.URL;

/**
 * @author zhenghui
 * @description: Javassist使用演示测试
 * @date 2021/4/6 6:38 下午
 */
public class JavassistTest2 {

    public static void main(String[] args) throws CannotCompileException, IOException {

        //找到本文件的路径,与之保存在一起
        URL resource = JavassistTest2.class.getClassLoader().getResource("");
        String file = resource.getFile();
        System.out.println("文件存储路径:"+file);

        ClassPool cp = ClassPool.getDefault();
        CtClass ctClass = cp.makeClass("com.ssdmbbl.javassist.Hello");

        //创建一个类名为"hello",传递参数的顺序为(int,double),没有返回值的类
        /*
        CtMethod(...)源代码:
        public CtMethod(CtClass returnType,//这个方法的返回值类型,
                        String mname, //(method name)方法的名字是什么
                        CtClass[] parameters, //方法传入的参数类型是什么
                        CtClass declaring //添加到哪个类中
                        ) {....}
         */
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
		//设置hello方法的权限为public
        ctMethod.setModifiers(Modifier.PUBLIC);

        //向ctClass中添加这个方法
        ctClass.addMethod(ctMethod);

        //写入本地
        ctClass.writeFile(file);
    }
}

执行后,就可以查看生成的代码了:

可以看到,我们并没有指定参数的名字,也会给生成var1、var2依次类推的名字。

var1和var2其实class变量表中存放的名字。

package com.ssdmbbl.javassist;

public class Hello {
    public void hello1(int var1, double var2) {
    }

    public Hello() {
    }
}

可以设置的返回值类型:

public static CtClass booleanType;
public static CtClass charType;
public static CtClass byteType;
public static CtClass shortType;
public static CtClass intType;
public static CtClass longType;
public static CtClass floatType;
public static CtClass doubleType;
public static CtClass voidType;

(二)新增一个变量

URL resource = JavassistTest2.class.getClassLoader().getResource("");
String file = resource.getFile();
System.out.println("文件存储路径:"+file);

ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.makeClass("com.ssdmbbl.javassist.Hello");

//添加一个hello1的方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctClass.addMethod(ctMethod);

//添加一个int类型的,名字为value的变量
CtField ctField = new CtField(CtClass.intType,"value",ctClass);
ctField.setModifiers(Modifier.PRIVATE);
ctClass.addField(ctField);

ctClass.writeFile(file);

那么执行后的内容就是如下了:

package com.ssdmbbl.javassist;

public class Hello {
    private int value;

    public void hello1(int var1, double var2) {
    }

    public Hello() {
    }
}

(三)给变量新增get和set方法

代码修改如下:

   public static void main(String[] args) throws CannotCompileException, IOException {

        URL resource = JavassistTest2.class.getClassLoader().getResource("");
        String file = resource.getFile();
        System.out.println("文件存储路径:"+file);

        ClassPool cp = ClassPool.getDefault();
        CtClass ctClass = cp.makeClass("com.ssdmbbl.javassist.Hello");

        //添加一个hello1的方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctClass.addMethod(ctMethod);

        //添加一个int类型的,名字为value的变量
        CtField ctField = new CtField(CtClass.intType,"value",ctClass);
        ctField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(ctField);

        //为value变量添加set方法
        CtMethod setValue = new CtMethod(CtClass.voidType, "setValue", new CtClass[]{CtClass.intType}, ctClass);
        setValue.setModifiers(Modifier.PUBLIC);
        ctClass.addMethod(setValue);

        //为value变量添加get方法
        CtMethod getValue = new CtMethod(CtClass.intType, "getValue", new CtClass[]{}, ctClass);
        getValue.setModifiers(Modifier.PUBLIC);
        ctClass.addMethod(getValue);

        ctClass.writeFile(file);
    }

执行效果:

package com.ssdmbbl.javassist;

public class Hello {
    private int value;

    public void hello1(int var1, double var2) {
    }

    public void setValue(int var1) {
    }

    public int getValue() {
    }

    public Hello() {
    }
}

(四)给方法内部添加代码

你是不是很好奇,set和get方法内部并没有代码,当程序运行的时候,肯定会出错的。
下面就来看一下。

我们预想的结果:

    private int value;

    public void setValue(int var1) {
        this.value = var1
    }

    public int getValue() {
        return this.value;
    }

修改如下:

//为value变量添加set方法
CtMethod setValue = new CtMethod(CtClass.voidType, "setValue", new CtClass[]{CtClass.intType}, ctClass);
setValue.setModifiers(Modifier.PUBLIC);
//设置方法体
setValue.setBody("this.value = var1;");

ctClass.addMethod(setValue);

//为value变量添加get方法
CtMethod getValue = new CtMethod(CtClass.intType, "getValue", new CtClass[]{}, ctClass);
getValue.setModifiers(Modifier.PUBLIC);
//设置方法体
getValue.setBody("return this.value;");
ctClass.addMethod(getValue);

很倒霉,说找不到这个var1这个变量

报错的堆栈信息如下:

Exception in thread "main" javassist.CannotCompileException: [source error] no such field: var1
at javassist.CtBehavior.setBody(CtBehavior.java:474)
at javassist.CtBehavior.setBody(CtBehavior.java:440)
at com.ssdmbbl.javassist.JavassistTest2.main(JavassistTest2.java:41)
Caused by: compile error: no such field: var1

这个原因我们前面其实提到了,因为在编译的时候,会把变量名抹掉,传递的参数会依次在局部变量表中的顺序。

如果传递:

public void test(int a,int b,int c){
    ...
}

那么a,b,c就对应本地变量表中的1,2,3的位置。

 —— ——
|a|1|
|b|2|
|c|3|
 —— ——

那么我们获取变量时就不能使用原始的名字了,在Javassist中访问方法中的参数使用的是$1, 2 , 2, 2,…,而不是直接使用原始的名字。

我们修改如下:

//为value变量添加set方法
CtMethod setValue = new CtMethod(CtClass.voidType, "setValue", new CtClass[]{CtClass.intType}, ctClass);
setValue.setModifiers(Modifier.PUBLIC);
//设置方法体
setValue.setBody("this.value = $1;");

ctClass.addMethod(setValue);

//为value变量添加get方法
CtMethod getValue = new CtMethod(CtClass.intType, "getValue", new CtClass[]{}, ctClass);
getValue.setModifiers(Modifier.PUBLIC);
//设置方法体
getValue.setBody("return this.value;");
ctClass.addMethod(getValue);

结果成功了:

public class Hello {
    private int value;

    public void hello1(int var1, double var2) {
    }

    public void setValue(int var1) {
        this.value = var1;
    }

    public int getValue() {
        return this.value;
    }

    public Hello() {
    }
}

再来一个:修改hello1方法体,使传递的两个参数相加然后赋值给value;

//添加一个hello1的方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("this.value = $1 + $2;");
ctClass.addMethod(ctMethod);

执行结果如下:

因为我们value是int,$1是int,$2是double,所以做了强制转型处理。

public void hello1(int var1, double var2) {
    this.value = (int)((double)var1 + var2);
}

(五)在方法体的前后分别插入代码

测试代码如下:

//添加一个hello1的方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("this.value = $1 + $2;");
ctMethod.insertBefore("System.out.println(\"我在前面插入了:\"+$1);");
ctMethod.insertAfter("System.out.println(\"我在最后插入了:\"+$1);");
ctClass.addMethod(ctMethod);

结果如下:

public void hello1(int var1, double var2) {
    System.out.println("我在前面插入了:" + var1);
    this.value = (int)((double)var1 + var2);
    Object var5 = null;
    System.out.println("我在最后插入了:" + var1);
}

三、Javassist中的一些特殊参数示例讲解

官方文档中看到有几个比较特殊的标识符,还有几个比较特殊的标识符需要了解。

标识符 作用
$0、$1、$2、 3 、 3、 3、… this和方法参数(1-N是方法参数的顺序)
$args 方法参数数组,类型为Object[]
$$ 所有方法参数,例如:m($$)相当于m($1,$2,…)
$cflow(…) control flow 变量
$r 返回结果的类型,在强制转换表达式中使用。
$w 包装器类型,在强制转换表达式中使用。
$_ 返回的结果值
$sig 类型为java.lang.Class的参数类型对象数组
$type 类型为java.lang.Class的返回值类型
$class 类型为java.lang.Class的正在修改的类

下面咱们来一起分别来看一下,分析一下,怎么用,有什么用吧。

只介绍几个常见和常用到的吧。
有兴趣的话,剩下的可以看官方文档:http://www.javassist.org/tutorial/tutorial2.html

(一)$0,$1,$2,…

这个其实咱们已经在上面用到过了,再来细说一下吧。$0代表this,$1, 2 , 2, 2,…,依次对应方法中参数的顺序。
如果有:

public void test(int a,int b,int c){}

那么如果想引用a和b和c的话,需要这样:

ctMethod.setBody("return $1 + $2 + $3;");

对了还有:静态方法是没有$0的,所以静态方法下$0是不可用的。

(二)$args

$args变量表示所有参数的数组,它是一个Object类型的数组(new Object[]{…}),如果参数中有原始类型的参数,会被转换成对应的包装类型。比如原始数据类型为int,则会被转换成java.lang.Integer,然后存储在args中。

例如我们测试代码如下:

CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("System.out.println($args);");

编译后的结果如下:

public void hello1(int var1, double var2) {
    System.out.println(new Object[]{new Integer(var1), new Double(var2)});
}

(三)$$

变量 是 所 有 参 数 的 缩 写 , 参 数 用 逗 号 分 割 , 例 如 : m ( 是所有参数的缩写,参数用逗号分割,例如:m( 是所有参数的缩写,参数用逗号分割,例如:m()相当于:m($1,$2,$3,…)。

如何使用呢?

我们经常做一些代码优化,把较为复杂的方法内部的逻辑,提炼出来,提炼到一个公共的方法里。

或者说一个方法调用另一个方法,这两个方法传递的参数是一样的时候就用到了。

例如原java:

public Object m1(String name,String age){
	...省略10000行代码逻辑
}

提炼后的:

提炼出来的代码,我们也可以在其他地方使用(所谓的公共的代码)。

public Object m1(String name,String age){
    ...省略20行代码逻辑
    return m2(name,age);
}

private Object m2(String name,String age){
	...
}

那么我们就造一个这个场景来说明一下用法吧。

简单点来说,就是一个方法调用另一个方法,传递全参数时。

CtMethod hello2 = new CtMethod(CtClass.voidType, "hello2", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
hello2.setModifiers(Modifier.PUBLIC);
hello2.setBody("this.value = $1 + $2;");
ctClass.addMethod(hello2);

//添加一个hello1的方法
CtMethod hello1 = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
hello1.setModifiers(Modifier.PUBLIC);
hello1.setBody("this.value = $1 + $2;");
hello1.insertAfter("hello2($$);");

可以看到我们hello1调用hello2时,需要传递全部参数。此时即可写成$$方法,当然了也可以写成hello2($1,$2)。

编译后的结果:

public void hello2(int var1, double var2) {
    this.value = (int)((double)var1 + var2);
}

public void hello1(int var1, double var2) {
    this.value = (int)((double)var1 + var2);
    Object var5 = null;
    this.hello2(var1, var2);
}

(四)$cflow(…)

$cflow 的全名为:control flow,这是一个只读变量,返回指定方法递归调用的深度。

我们以计算n的斐波拉契数列为例,来演示一下如何使用。

我们正确的递归算法代码如下:

public int f(int n){
	if(n <= 1){
    	return n;
    }
    return f(n-1) + f(n - 2)
 }

对于上面这段代码,我们可以这样写:

CtMethod f = new CtMethod(CtClass.intType,"f", new CtClass[]{CtClass.intType}, ctClass);
f.setBody("{if($1 <= 1){" +
          "     return $1;" +
          "}" +
          "return f($1 - 1) + f( $1 - 2);}");
f.setModifiers(Modifier.PUBLIC);

编译后的:

public int f(int var1) {
    return var1 <= 1 ? var1 : this.f(var1 - 1) + this.f(var1 - 2);
}

那么我们只想在递归到前n次的时候打印log,我们该怎么做呢。

例如,我们下面写的是"$cflow(f) == 1"时

CtMethod f = new CtMethod(CtClass.intType,"f", new CtClass[]{CtClass.intType}, ctClass);
f.setBody("{if($1 <= 1){" +
          "     return $1;" +
          "}" +
          "return f($1 - 1) + f( $1 - 2);}");
f.setModifiers(Modifier.PUBLIC);

//在代码body的前面插入
f.useCflow("f");
f.insertBefore("if($cflow(f) == 1){" +
               "               System.out.println(\"我执行了,此时的n是:\"+$1);" +
               "           }");

编译后的代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.ssdmbbl.javassist;

import javassist.runtime.Cflow;

public class Hello {
    public static Cflow _cflow$0 = new Cflow();

    public int f(int var1) {
        if (_cflow$0.value() == 1) {
            System.out.println("我执行到来第2次,此时的n是:" + var1);
        }

        boolean var6 = false;

        int var10000;
        try {
            var6 = true;
            _cflow$0.enter();
            if (var1 <= 1) {
                var10000 = var1;
                var6 = false;
            } else {
                var10000 = this.f(var1 - 1) + this.f(var1 - 2);
                var6 = false;
            }
        } finally {
            if (var6) {
                boolean var3 = false;
                _cflow$0.exit();
            }
        }

        int var8 = var10000;
        _cflow$0.exit();
        return var8;
    }

    public Hello() {
    }
}

通过查看源码,发先关键的一个地方是调用了Cflow对象的enter方法:

public static Cflow _cflow$0 = new Cflow();

...

_cflow$0.enter();

点进enter()的内部发现,调用了get().inc()方法:

public class Cflow extends ThreadLocal<Cflow.Depth> {
    protected static class Depth {
        private int depth;
        Depth() { depth = 0; }
        int value() { return depth; }
        void inc() { ++depth; }
        void dec() { --depth; }
    }

    @Override
    protected synchronized Depth initialValue() {
        return new Depth();
    }

    /**
     * Increments the counter.
     */
    public void enter() { get().inc(); }

    /**
     * Decrements the counter.
     */
    public void exit() { get().dec(); }

    /**
     * Returns the value of the counter.
     */
    public int value() { return get().value(); }
}

而inc()方法控制着一个全局变量的增加操作。

void inc() { ++depth; }

boolean var6 = false;相当于是一个开关,控制着是否开始和结束。

当参数var1<=1时,即结束

if (var1 <= 1) {
    var10000 = var1;
    var6 = false;
}

我们可以使用反射来验证一下,测试代码如下:

package com.ssdmbbl.javassist;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author zhenghui
 * @description:
 * @date 2021/4/8 10:20 上午
 */
public class Test {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {

        Class<?> aClass = Class.forName("com.ssdmbbl.javassist.Hello");

        //初始化这个类
        Object obj = aClass.newInstance();

        //获取所有的方法
        Method[] methods = aClass.getMethods();
        //遍历所有的方法
        for (Method method : methods) {
            //当方法为f的时候,进行调用
            if(method.getName().equals("f")){
                //调用并传递参数为5,即f(5)
                method.invoke(obj,5);
            }
        }
    }
}

执行的结果:

为什么是2次呢?原因上面也说了:if(var1 <= 1){…},所以是

我执行了,此时的n是:4
我执行了,此时的n是:3

相关链接

1、javassist的API接口文档

http://www.javassist.org/html/index.html

2、javassist的github开源地址

https://github.com/jboss-javassist/javassist

3、javassist的官网

http://www.javassist.org/

4、javassist官方的英文教程

http://www.javassist.org/tutorial/tutorial.html

到此这篇关于详解Java字节码编程之非常好用的javassist 的文章就介绍到这了,更多相关Java javassist 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Javassist如何操作Java 字节码

    一.开篇 说起 AOP 小伙伴们肯定很熟悉,无论是 JDK 动态代理或者是 CGLIB 等,其底层都是通过操作 Java 字节码来实现代理.常用的一些操作字节码的技术有 ASM.AspectJ.Javassist 等. ASM 其设计和实现是尽可能小而且快,更专注于性能.它在指令的层面来操作,所以使用它需要对 JVM 的指令有所了解,门槛较高,CGLIB 就使用了 ASM 技术. AspectJ 扩展了 Java 语言,定义了一系列 AOP 语法,在 JVM 中运行需要使用特定的编译器生成遵守

  • java 中如何获取字节码文件的相关内容

    java 中如何获取字节码文件的相关内容 反射机制是指在运行状态中,对任意一个类(class文件),都能知道这个类的所有属性和方法:对任意一个对象,都能调用这个对象的方法和属性.这种动态的获取信息和动态的调用对象的方法的功能称为--Java语言的反射机制. 简单点说,动态的获取类中的信息,这就是Java的反射机制. 在Java的反射机制中,我们可以通过配置文件信息,然后通过类名来获取类中包含的详细信息,如构造函数.成员变量和成员函数等.在接下来,作者将分别演示如何通过类名来获取类中包含的信息.

  • Java字节码中jvm实例用法

    要想使Java运行,我们可以设计一个面向Java语言特性的虚拟机,并通过编译器将Java程序转换为它可以识别的指令序列,也称为Java字节码.由于Java字节码指令的操作码被固定为一个字节,所以它的名字就这样命名了.本篇要带来的是Java字节码中jvm的使用,主要分为两个操作视角,一起来看看具体内容吧. 1.虚拟机视角 在执行Java代码时,首先需要将编译后的class文件装入Java虚拟机.装入的Java类将存储在方法区(MethodArea)中.虚拟机会在实际运行时执行方法区内的代码.Jav

  • java 获取字节码文件的几种方法总结

    java 获取字节码文件的几种方法总结 在本文中,以Person类为例,将分别演示获取该类字节码文件的三种方式, 其具体思想及代码如下所示: public class Person { private int age; private String name; public Person() { System.out.println("person run"); } public Person(String name, int age) { this.age = age; this.n

  • java字节码框架ASM操作字节码的方法浅析

    之前我们已经对ASM进行的详细的介绍,需要的朋友们可以点击这里:java字节码框架ASM的深入学习 JVM的类型签名对照表 Type Signature Java Type Z boolean B byte C char S short I int J long F float D double L fully-qualified-class ;fully-qualified-class [ type type[] ( arg-types ) ret-type method type 比如,ja

  • java字节码框架ASM的深入学习

    一.什么是ASM ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能.ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为.Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称.方法.属性以及 Java 字节码(指令).ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类. 使用ASM框架需要导入asm的jar包,下载链接:

  • 浅谈javap命令拆解字节码文件

    目的拆解分析反编译字节码 解析成人能够理解的结构 ,然后再对字节码文件进一步分析 源代码 public class test { private static int classV =2; public static void main(String[] args) { classV =200; int localV =4; localV =400; } } 二进制 idea bin_ed插件查看. 看不懂 那就使用人能看的懂的汇编语言查看类文件结构和代码指令. javap 指令和选项 0:无选

  • java获取版本号及字节码编译版本方法示例

    前言 之所以会有这篇文章,是因为公司的开发环境比较老,寻找一些jar包的时候总是会纠结对应的编译版本,感觉很麻烦,所以写了一个工具类用于读取class或jar文件的编译版本,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 示例代码 package com.jinggujin.util; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.

  • 详解Java动态字节码技术

    对 Debug 的好奇 初学 Java 时,我对 IDEA 的 Debug 非常好奇,不止是它能查看断点的上下文环境,更神奇的是我可以在断点处使用它的 Evaluate 功能直接执行某些命令,进行一些计算或改变当前变量. 刚开始语法不熟经常写错代码,重新打包部署一次代码耗时很长,我就直接面向 Debug 开发.在要编写的方法开始处打一个断点,在 Evaluate 框内一次次地执行方法函数不停地调整代码,没问题后再将代码复制出来放到 IDEA 里,再进行下一个方法的编写,这样就跟写 PHP 类似的

  • 详解Java字节码编程之非常好用的javassist

    一.Javassist入门 (一)Javassist是什么 Javassist是可以动态编辑Java字节码的类库.它可以在Java程序运行时定义一个新的类,并加载到JVM中:还可以在JVM加载时修改一个类文件.Javassist使用户不必关心字节码相关的规范也是可以编辑类文件的. (二)Javassist核心API 在Javassist中每个需要编辑的class都对应一个CtCLass实例,CtClass的含义是编译时的类(compile time class),这些类会存储在Class Poo

  • 详解python字节码

    Python对不可变序列进行重复拼接操作效率会很低,因为每次都会生成一个新的对象,解释器需要把原来对象中的元素先复制到新的对象里,然后再追加新的元素. 但是CPython对字符串操作进行了优化,因为对字符串做+=操作实在是太普遍了.因此,初始化str时会预留出额外的可扩展空间,从而进行增量操作的时候不会有复制再追加的这个步骤. 通过字节码研究一下这个过程. >>> s_code = 'a += "b"' >>> c = compile(s_code,

  • 详解Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)

    本文会从传统的BIO到NIO再到AIO自浅至深介绍,并附上完整的代码讲解. 下面代码中会使用这样一个例子:客户端发送一段算式的字符串到服务器,服务器计算后返回结果到客户端. 代码的所有说明,都直接作为注释,嵌入到代码中,看代码时就能更容易理解,代码中会用到一个计算结果的工具类,见文章代码部分. 相关的基础知识文章推荐: Linux 网络 I/O 模型简介(图文) Java 并发(多线程) 1.BIO编程 1.1.传统的BIO编程 网络编程的基本模型是C/S模型,即两个进程间的通信. 服务端提供I

  • 详解Java中多进程编程的实现

    1.Java进程的创建 Java提供了两种方法用来启动进程或其它程序: (1)使用Runtime的exec()方法 (2)使用ProcessBuilder的start()方法 1.1 ProcessBuilder     ProcessBuilder类是J2SE 1.5在java.lang中新添加的一个新类,此类用于创建操作系统进程,它提供一种启动和管理进程(也就是应用程序)的方法.在J2SE 1.5之前,都是由Process类处来实现进程的控制管理. 每个 ProcessBuilder 实例管

  • 详解Java图形化编程中的鼠标事件设计

    鼠标事件的事件源往往与容器相关,当鼠标进入容器.离开容器,或者在容器中单击鼠标.拖动鼠标时都会发生鼠标事件.java语言为处理鼠标事件提供两个接口:MouseListener,MouseMotionListener接口. MouseListener接口 MouseListener接口能处理5种鼠标事件:按下鼠标,释放鼠标,点击鼠标.鼠标进入.鼠标退出.相应的方法有: (1) getX():鼠标的X坐标 (2) getY():鼠标的Y坐标 (3) getModifiers():获取鼠标的左键或右键

  • 详解Java的设计模式编程中的原型模式

    定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象. 类型:创建类模式 类图: 原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype.Prototype类需要具备以下两个条件: 实现Cloneable接口.在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法.在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedExcepti

  • Java字节码增强技术知识点详解

    简单介绍下几种java字节码增强技术. ASM ASM是一个Java字节码操控框架,它能被用来动态生成类或者增强既有类的功能.ASM可以直接产生class文件,也可以在类被加载入Java虚拟机之前动态改变类行为.ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类. 主页:https://asm.ow2.io/index.html ASM框架中的核心类有以下几个: ① ClassReader:该类用来解析编译过的class字节码文件. ② ClassWriter:

  • 详解Java中的字节码增强技术

    目录 1.字节码增强技术 2.常见技术 3.ASM 3.1 测试 Main 3.2 测试 CustomerClassVisitor 3.3 测试 Test 1.字节码增强技术 字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术. 参考地址 2.常见技术 技术分类 类型 静态增强 AspectJ 动态增强 ASM.Javassist.Cglib.Java Proxy 3.ASM <dependency> <groupId>org.ow2.asm</gro

随机推荐