Java运行时动态生成类

1、自己动手,从零开始创建字节码,理论上可行,实际上很难

2、CGLib(代码很难看懂)

Enhancer e = new Enhancer();
e.setSuperclass(...);
e.setStrategy(new DefaultGeneratorStrategy() {
  protected ClassGenerator transform(ClassGenerator cg) {
    return new TransformingGenerator(cg,
      new AddPropertyTransformer(new String[]{ "foo" },
          new Class[] { Integer.TYPE }));
  }});
Object obj = e.create();

3、java内置编译器

(1)直接读取java文件

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int compilationResult = compiler.run(null, null, null, '/path/Test.java');

问题是我们在内存中创建了Java代码后,必须先写到文件,再编译,最后还要手动读取class文件内容并用一个ClassLoader加载。

(2)、Compiler直接在内存中完成编译,输出的class内容就是byte[]

Map<String, byte[]> results;
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
  JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);
  CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));
  if (task.call()) {
    results = manager.getClassBytes();
  }
}

上述代码的几个关键在于:

  • 用MemoryJavaFileManager替换JDK默认的StandardJavaFileManager,以便在编译器请求源码内容时,不是从文件读取,而是直接返回String;
  • 用MemoryOutputJavaFileObject替换JDK默认的SimpleJavaFileObject,以便在接收到编译器生成的byte[]内容时,不写入class文件,而是直接保存在内存中。

最后,编译的结果放在Map<String, byte[]>中,Key是类名,对应的byte[]是class的二进制内容。

为什么编译后不是一个byte[]呢?

因为一个.java的源文件编译后可能有多个.class文件!只要包含了静态类、匿名类等,编译出的class肯定多于一个。

(3)加载编译后的类

class MemoryClassLoader extends URLClassLoader {

  Map<String, byte[]> classBytes = new HashMap<String, byte[]>();

  public MemoryClassLoader(Map<String, byte[]> classBytes) {
    super(new URL[0], MemoryClassLoader.class.getClassLoader());
    this.classBytes.putAll(classBytes);
  }

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    byte[] buf = classBytes.get(name);
    if (buf == null) {
      return super.findClass(name);
    }
    classBytes.remove(name);
    return defineClass(name, buf, 0, buf.length);
  }
}

使用自己编写的类加载器加载

4、com.itranswarp.compiler实现

5、利用Groovy脚本实现

6、Javassist实现

Javassist中最为重要的是ClassPoolCtClass ,CtMethod 以及 CtField这几个类。

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

CtClass:表示一个类,这些CtClass对象可以从ClassPool获得。

CtMethods:表示类中的方法。

CtFields :表示类中的字段。

使用例子

 public void DynGenerateClass() {
     ClassPool pool = ClassPool.getDefault();
     CtClass ct = pool.makeClass("top.ss007.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();
     }
 }

è¿éåå¾çæè¿°

动态修改方法体(AOP)

动态的修改一个方法的内容才是我们关注的重点,例如在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("top.ss007.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); System.out.println(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),1,2);
        }
        ...
    }

结果:

  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);System.out.println(this.y);
  }

参考:

1-5:Java运行时动态生成类实现过程详解

6:Javassist中文技术文档  | 官网(可谷歌翻译)|  秒懂Java动态编程(Javassist研究)

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值