动态编程

动态编程是相对于静态编程而言的,静态编程语言例如Java,动态编程语言例如JavaScript。两者的区别简单的说就是在静态编程中,类型检查是在编译时完成的,而动态编程中类型检查是在运行时完成的。所谓动态编程就是绕过编译过程在运行时进行操作,在Java中有如下三种常见方式:

  • 反射

动态获取的信息以及动态调用对象的方法。

  • 动态编译

动态编译是从Java 6开始支持的,主要是通过一个JavaCompiler接口来完成的。通过这种方式我们可以直接编译一个已经存在的java文件,也可以在内存中动态生成Java代码,动态编译执行。

  • 动态生成字节码

通过操作Java字节码的方式在JVM中生成新类或者对已经加载的类动态添加元素

Javassit使用

操作java字节码的工具有两个比较流行,一个是ASM,一个是Javassit。
ASM :直接操作字节码指令,执行效率高,要是使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。
Javassit: 提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低。

Javassist是一个开源的分析、编辑和创建Java字节码的类库。通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。这点可以参考

Javassist中最为重要的是ClassPool,CtClass ,CtMethod 以及 CtField这几个类(和反射4大核心类很相似吧)。

ClassPool:一个基于HashMap实现的CtClass对象容器,其中键是类名称,值是表示该类的CtClass对象。默认的ClassPool使用与底层JVM相同的类路径,因此在某些情况下,可能需要向ClassPool添加类路径或类字节。
CtClass:表示一个类,这些CtClass对象可以从ClassPool获得。
CtMethods:表示类中的方法。
CtFields :表示类中的字段。

动态生成一个类

下面的代码会生成一个实现了Cloneable接口的类GenerateClass

public void DynGenerateClass() {
     ClassPool pool = ClassPool.getDefault();
     CtClass ct = pool.makeClass("my001.GenerateClass");//创建类
     ct.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});//让类实现Cloneable接口
     try {
         CtField f= new CtField(CtClass.intType,"id",ct);//获得一个类型为int,名称为id的字段
         f.setModifiers(AccessFlag.PUBLIC);//将字段设置为public
         ct.addField(f);//将字段设置到类上
         //添加构造函数
         CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
         ct.addConstructor(constructor);
         //添加方法
         CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
         ct.addMethod(helloM);

         ct.writeFile();//将生成的.class文件保存到磁盘

         //下面的代码为验证代码
         Field[] fields = ct.toClass().getFields();
         System.out.println("属性名称:" + fields[0].getName() + "  属性类型:" + fields[0].getType());
     } catch (CannotCompileException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     } catch (NotFoundException e) {
         e.printStackTrace();
     }
 }

上面的代码就会动态生成一个.class文件,查看生成的字节码文件GenerateClass.class:

package  my001;
public class GenerateClass implements Cloneable
{
    public int id;
}

动态添加构造函数及方法

有很多种方法添加构造函数,我们使用CtNewConstructor.make,他是一个的静态方法,如下所示。第一个参数是source text 类型的方法体,第二个为类对象。

CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
ct.addConstructor(constructor);   

这段代码执行后会生成如下java代码,代码片段是使用反编译工具产生的,可以看到构造函数的参数名被修改成了paramInt。

public GeneratedClass(int paramInt)
  {
    this.id = paramInt;
  }

同样有很多种方法添加函数,我们使用CtNewMethod.make这个比较简单的形式

CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
ct.addMethod(helloM);

这段代码执行后会生成如下java代码:

public void hello(String paramString)
  {
    System.out.println(paramString);
  }

动态修改方法体

在AOP编程方面,我们就会用到这种技术,动态的在一个方法中插入代码。
例如我们有下面这样一个类

public class Point {
    private int x;
    private int y;

    public Point(){}
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void move(int dx, int dy) {
        this.x += dx;
        this.y += dy;
    }
}

我们要动态的在内存中在move()方法体的前后插入一些代码

public void modifyMethod()
    {
        ClassPool pool=ClassPool.getDefault();
        try {
            CtClass ct=pool.getCtClass("my001.Point");
            CtMethod m=ct.getDeclaredMethod("move");
            m.insertBefore("{ System.out.print(\"dx:\"+$1); System.out.println(\"dy:\"+$2);}");
            m.insertAfter("{System.out.println(this.x+" "+this.y);}");

            ct.writeFile();
            //通过反射调用方法,查看结果
            Class pc=ct.toClass();
            Method move= pc.getMethod("move",new Class[]{int.class,int.class});
            Constructor<?> con=pc.getConstructor(new Class[]{int.class,int.class});
            move.invoke(con.newInstance(1,2),2,3);
        }
        ...
    }

使用反编译工具查看修改后的move方法结果:

public void move(int dx, int dy) {
    System.out.print("dx:" + dx);System.out.println("dy:" + dy);
    this.x += dx;
    this.y += dy;
    Object localObject = null;//方法返回值
    System.out.println(this.x+" "+this.y);
  }

可以看到,在生成的字节码文件中确实增加了相应的代码。
函数输出结果为:

dx:1 dy:2
3
5

参考文章链接

java动态编程-简书
java动态编程初探-javassist-博客园

Last modification:June 18th, 2020 at 10:07 am