欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java编程的动态特性,使用BCEL代码生成工具,极大简化Class Transformation开发 Java字节码虚拟机BCELClassTransformer

程序员文章站 2024-03-20 23:50:34
...
 
       在写过《Java编程的动态特性,从Reflection到Runtime Class Transformation》入门日记之后,笔者继续在此方向上实践了2天的日夜煎熬,最终实现了最初的想法。然而在临末之时忽然发现一条捷径,倘若早采用这条捷径,将会少走很多弯路,便可以更快的完成最初的目标。所以本篇入门日记主要介绍这条捷径,以便各位搞技术的兄弟姐妹待用之时便可以少走很多弯路且节省很多时间。
     为了让没做过类似工作的读者能同笔者一样身临其境,故在介绍
该捷径之后简要的说明一下笔者的设计初衷以及,不感兴趣的直接跳过。
假设有实体类ServiceOrder,它有一个方法allocateResource,笔者期望在运行时拦截这个allocateResource方法调用。样例类如下:

@LifecycleMeta(ServiceableLifecycleMeta.class)
@Entity 

public class ServiceOrder {

    @Transition(Schedule.class)
    public void allocateResource(){
        //...
    }


第一步设计运行时拦截这个方法的实现,如下: 

@LifecycleMeta(ServiceableLifecycleMeta.class)

@Entity
public
 class ServiceOrder {
     @Transition(Schedule.class)
     public void allocateResource() {
             final InterceptController controller = new InterceptController();
             final InterceptContext context = new InterceptContext(this, "allocateResource", new Class[]{});
             controller.exec(context, new java.util.concurrent.Callable<java.lang.Void>() {
                 allocateResource$Impl();
                 return null;
             }
     }

      @Transition(Schedule.class)
      public void allocateResource$Impl() {
         // original logic
      }


第二步,设计好可编译的目标代码样式后,编译出class文件。
第三步,然后通过BCEL提供的一个工具类(捷径)BCELifier来生成Java代码,这个代码就是如何生成目标class文件的代码,如下:
Java编程的动态特性,使用BCEL代码生成工具,极大简化Class Transformation开发
            
    
    
        Java字节码虚拟机BCELClassTransformer
后面是生成到System.out的代码片段

Java编程的动态特性,使用BCEL代码生成工具,极大简化Class Transformation开发
            
    
    
        Java字节码虚拟机BCELClassTransformer

第四步,根据一些变化来改写这个代码。前提是需要有一点字节码,本地变量表,操作数栈等得基本知识,就如上一篇日记中提到的那些入门基础和路线,这些内容都比较容易理解。
比如优化减少指令,比如通过ldc直接加载当前的class,而不需要通过this.getClass()来获得。
比如拦截的方法可以接受参数,可以接受基本数据类型的参数或者引用类型参数或者数组。
比如方法的返回值处理等等。
第五步,测试一下生成的代码,可以通过写test drive来运行它,或者通过Byte Code Visualizer以及Class File Editor来查看生成的方法的指令序列和Class ConstantPool中的常量。迭代第四步和第五步,就可以完成主要字节码转换的主要工作了。
笔者做的工作是一个指令一个指令自己写上去的,走了很多弯路,比如在处理如何在字节码层面如何引用基本数据类型的Class对象,匿名内置类和outer class之间的各种Class属性设置等等,而这些在生成的代码中都有确切的指令,如上面图片中18行左右的createFieldAccess部分。笔者从来没有使用过java.lang.Long.TYPE这个静态常量,原来这就是基础数据类型long对应的Class对象,其他的基本数据类型同上。
 
 设计初衷
如果对设计不感兴趣,可以直接跳过后面的片段。
     笔者初衷是要实现方法拦截。众所周知实现方法拦截可以有不少办法,比如如果是在非框架下工作,那么工程师自己可以采用动态代理的方式;或者可以采用代理的设计模式;或者用一些“高级”AOP的工具;在框架下工作可以采用标准组件框架下提供的组件,比如在EJB环境下工作,对于EJB业务方法的拦截可以采用JavaEE的Interceptor,但是如果相对非组件类方法进行拦截,标准化组件框架并不提供类似的功能。在上面介绍的这么多方法拦截实现手段中,出了现有的AOP工具外(修改字节码的实现方式),大都有一些额外的开销。一般需要Java反射加动态代理,且需要在设计的架构中单独增加一层来实现。若不采用Java反射和动态代理,则需要增加一层非常具体的接口层。如果这些接口层本身就是架构设计者需要的部分,那么无可厚非;倘若是为了封装和隐藏框架的行为而特意设计的这样的接口层,就显得有些笨重了,程序员在编写代码的时候总会显得不自然。最臭名昭著的就是早期的EJB1.x和EJB2.x,应用程序员为了编写业务逻辑必须额外实现框架强制要求的接口,而且还要编写标准的部署描述符文件以及容器指定的部署描述符文件才能运行自己的程序。总而言之,为了在框架中实现方法拦截(包括声称为应用提供很多服务,如安全性服务,事务服务,消息服务,邮件服务等等)而产生额外的编码需求,这是不受欢迎的。作者的初衷就是在要提供额外服务的前提下,避免增加额外的编码工作,使这些服务的运行犹如春雨般,润物细无声。
在介绍完笔者的初衷后,我们更进一步看看细节。对于web或者企业应用开发者来说,一直以来大家都在争论“充血型”模型和“贫血型”模型的优劣,只有开始,没有结束。我们不在这里谈论这个没有结论的话题,倒是可以引用Martin Fowler的一句话以及一个大原则。M大师说,“尽管面向对象编程语言已经占领了编程语言的市场,然而主宰编程思想的依旧是面向过程”。颇有讽刺的意味啊!大家都在用牛刀杀鸡。另外在做架构设计时总会碰到一些纠结的问题,就比如“贫血型”和“充血型”(别担心我们不会谈论这个),人们总是纠结于到底要在业务层或者实体层暴漏什么给其他的层次。M大师一言以蔽之,要多思考隐藏(封装)什么,而不是暴漏什么。M大师提到通常他自己要隐藏的不仅仅是实现,而更多的是设计。关于M大师我们就说到这里。笔者喜欢面向对象的封装,倾向于“充血型”模型。旨在实体层次中封装(隐藏)业务对象的生命周期管理。就像RESTful充分利用HTTP协议一样,笔者要充分利用JPA框架中的各种工具。