服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

java中通过emit实现动态类生成


  动态生成一个类对于aop,o/r mapping等技术非常有帮助。对于java来说,问题不大,而对于.net,则要麻烦些(主要麻烦在于实现代码的生成需要il),故猜测这可能也是在aop, o/r mapping方面,java走得略前的原因吧。

  麻烦归麻烦,非不能也,动态生成一个简单的类还不至于太难。

  假设有如下接口:

interface ianimal
{
 void move();
 void eat();
}

  希望能创建一个类生成器typecreator,并能以以下方式使用:

typecreator tc=new typecreator(typeof(ianimal));
type t = tc.build();
ianimal myanimal= (ianimal)activator.createinstance(t);
myanimal.move();
myanimal.eat();

  首先,发现system.reflection.emit.typebuilder似乎就是一个现成的类生成器。 不过typebuilder既没有实用的static方法,也不能在外部实例化。不过modulebuilder倒有一个definetype()方法,可以得到typebuilder;而modulebuilder和typerbuilder一个德行,不能直接创建,得从assemblybuilder的definedynamicmodule()方法得到。追根溯源,assemblybuilder得从appdomain的definedynamicassembly()的得来。最终好在appdomain提供了一个静态方法:appdomain.currentdomain. 这一连串并非没有道理,类型是依附于module的,而module依附于assembly,而assembly则被appdomain装载。所谓“皮之不存,毛将焉附”,为了创建type这个“毛”,得先把assembly,module这些“皮”依次构造出来:

using system;
using system.reflection;
using system.reflection.emit;

public class typecreator
{
 private type targettype;

 /// <summary>
 /// 构造函数
 /// </summary>
 /// <param name="targettype">被实现或者继承的类型</param>
 public typecreator(type targettype)
 {
  this.targettype = targettype;
 }

 public type build()
 {
  //获取当前appdomain
  appdomain currentappdomain = appdomain.currentdomain;

  //system.reflection.assemblyname 是用来表示一个assembly的完整名称的
  assemblyname assyname = new assemblyname();

  //为要创建的assembly定义一个名称(这里忽略版本号,culture等信息)
  assyname.name = "myassyfor_" + targettype.name;

  //获取assemblybuilder
  //assemblybuilderaccess有run,save,runandsave三个取值
  assemblybuilder assybuilder = currentappdomain.definedynamicassembly(assyname,assemblybuilderaccess.run);

  //获取modulebuilder,提供string参数作为module名称,随便设一个
  modulebuilder modbuilder = assybuilder.definedynamicmodule("mymodfor_"+targettype.name);

  //新类型的名称:随便定一个
  string newtypename = "imp_"+targettype.name;

  //新类型的属性:要创建的是class,而非interface,abstract class等,而且是public的
  typeattributes newtypeattribute = typeattributes.class | typeattributes.public;

  //声明要创建的新类型的父类型
  type newtypeparent;

  //声明要创建的新类型要实现的接口
  type[] newtypeinterfaces;

  //对于基类型是否为接口,作不同处理
  if(targettype.isinterface)
  {
   newtypeparent = null;
   newtypeinterfaces = new type[]{targettype};
  }
  else
  {
   newtypeparent = targettype;
   newtypeinterfaces = new type[0];
  }

  //得到类型生成器
  typebuilder typebuilder = modbuilder.definetype(newtypename,newtypeattribute,newtypeparent,newtypeinterfaces);

  //以下将为新类型声明方法:新类型应该override基类型的所以virtual方法

  //得到基类型的所有方法
  methodinfo[] targetmethods = targettype.getmethods();

  //遍历各个方法,对于virtual的方法,获取其签名,作为新类型的方法
  foreach(methodinfo targetmethod in targetmethods)
  {
   //只挑出virtual的方法
   if(targetmethod.isvirtual)
   {
    //得到方法的各个参数的类型
    parameterinfo[] paraminfo = targetmethod.getparameters();
    type[] paramtype = new type[paraminfo.length];
    for(int i=0;i<paraminfo.length;i++)
     paramtype[i] = paraminfo[i].parametertype;

     //传入方法签名,得到方法生成器
     methodbuilder methodbuilder = typebuilder.definemethod(targetmethod.name,methodattributes.public|
       methodattributes.virtual,targetmethod.returntype,paramtype);

     //由于要生成的是具体类,所以方法的实现是必不可少的。而方法的实现是通过emit il代码来产生的
   
     //得到il生成器
     ilgenerator ilgen = methodbuilder.getilgenerator();
     //以下三行相当于:{console.writeln("i'm "+ targetmethod.name +"ing");}
     ilgen.emit(opcodes.ldstr,"i'm "+ targetmethod.name +"ing");
     ilgen.emit(opcodes.call,typeof(console).getmethod("writeline",new type[]{typeof(string)}));
     ilgen.emit(opcodes.ret);
   }
  }
  //真正创建,并返回
  return(typebuilder.createtype());
 }
}

  好了,测试一下试试看:

using system;

public class tester
{
 public static void main(string[] args)
 {
  typecreator tc=new typecreator(typeof(ianimal));
  type t = tc.build();
  ianimal animal= (ianimal)activator.createinstance(t);
  animal.move();
  animal.eat();

  console.read ();
 }
}

  得到输出:i'm moveingi'm eating。

  总结

  如果用于aop的话,emit可以动态生成一个装饰类,相比于基于remoting架构的tp/rp的方法,效率可能要高些,而且还能拦截new操作符。缺点:对于非virtual的方法,似乎无法拦截。用于o/r mapping的类生成,倒是不错。

扫描关注微信公众号