龙盟编程博客 | 无障碍搜索 | 云盘搜索神器
快速搜索
主页 > 软件开发 > JAVA开发 >

Java 1.5中面向方面(AOP)编程[组图](5)

时间:2009-12-23 15:42来源:未知 作者:admin 点击:
分享到:
用于该项操作的BCEL 5.1版本有一个问题--它不支持分析注解。我可以载入正在重构的类并使用反射(reflection)查看注解。但是,如果这样,我就不得不使用

  用于该项操作的BCEL 5.1版本有一个问题--它不支持分析注解。我可以载入正在重构的类并使用反射(reflection)查看注解。但是,如果这样,我就不得不使用RetentionPolicy.RUNTIME来代替RetentionPolicy.CLASS。我还必须在这些类中执行一些静态的初始化,而这些操作可能载入本地类库或引入其它的依赖关系。幸运的是,BCEL提供了一种插件(plugin)机制,它允许客户端分析字节码属性。我编写了自己的AttributeReader的实现(implementation),在出现注解的时候,它知道如何分析插入字节码中的RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations属性。BCEL未来的版本应该会包含这种功能而不是作为插件提供。

  编译时刻的字节码重构方法显示在示例代码的code/02_compiletime目录中。

  但是这种方法有很多缺陷。首先,我必须给建立过程增加额外的步骤。我不能基于命令行设置或其它编译时没有提供的信息来决定打开或关闭重构操作。如果重构的或没有重构的代码需要同时在产品环境中运行,那么就必须建立两个单独的.jars文件,而且还必须决定使用哪一个。
    在类载入时重构字节码

  更好的方法可能是延迟字节码重构操作,直到字节码被载入的时候才进行重构。使用这种方法的时候,重构的字节码不用保存起来。我们的应用程序启动时刻的性能可能会受到影响,但是你却可以基于自己的系统属性或运行时配置数据来控制进行什么操作。

   Java 1.5之前,我们使用定制的类载入程序可能实现这种类文件维护操作。但是Java 1.5中新增加的java.lang.instrument程序包提供了少数附加的工具。特别地,它定义了ClassFileTransformer的概念,在标准的载入过程中我们可以使用它来重构一个类。

  为了在适当的时候(在载入任何类之前)注册ClassFileTransformer,我需要定义一个premain方法。Java在载入主类(main class)之前将调用这个方法,并且它传递进来对Instrumentation对象的引用。我还必须给命令行增加-javaagent参数选项,告诉Java我们的premain方法的信息。这个参数选项把我们的agent class(代理类,它包含了premain方法)的全名和任意字符串作为参数。在例子中我们把Instrumentor类的全名作为参数(它必须在同一行之中):

  -javaagent:boxpeeking.instrument.InstrumentorAdaptor=
  boxpeeking.status.instrument.StatusInstrumentor

  现在我已经安排了一个回调(callback),它在载入任何含有注解的类之前都会发生,并且我拥有Instrumentation对象的引用,可以注册我们的ClassFileTransformer了:

  public static void premain (String className,
  Instrumentation i)
  throws ClassNotFoundException,
  InstantiationException,
  IllegalAccessException
  {
  Class instClass = Class.forName(className);
  Instrumentor inst = (Instrumentor)instClass.newInstance();
  i.addTransformer(new InstrumentorAdaptor(inst));
  }


  我们在此处注册的适配器将充当上面给出的Instrumentor接口和Java的ClassFileTransformer接口之间的桥梁。


  public class InstrumentorAdaptor
  implements ClassFileTransformer
  {
  public byte[] transform (ClassLoader cl,String className,Class classBeingRedefined,
  ProtectionDomain protectionDomain,byte[] classfileBuffer)
  {
  try {
   ClassParser cp =new ClassParser(new ByteArrayInputStream(classfileBuffer),className + ".java");
   JavaClass jc = cp.parse();

   ClassGen cg = new ClassGen(jc);

   for (Annotation an : getAnnotations(jc.getAttributes())) {
    instrumentor.instrumentClass(cg, an);
   }

   for (org.apache.bcel.classfile.Method m : cg.getMethods()) {
    for (Annotation an : getAnnotations(m.getAttributes())) {
     ConstantPoolGen cpg =cg.getConstantPool();
     MethodGen mg =new MethodGen(m, className, cpg);
     instrumentor.instrumentMethod(cg, mg, an);
     mg.setMaxStack();
     mg.setMaxLocals();
     cg.replaceMethod(m, mg.getMethod());
    }
   }
   JavaClass jcNew = cg.getJavaClass();
   return jcNew.getBytes();
  } catch (Exception ex) {
   throw new RuntimeException("instrumenting " + className, ex);
  }
  }
  ...
  }

  这种在启动时重构字节码的方法位于在示例的/code/03_startup目录中。

精彩图集

赞助商链接