天天看点

【ASM系列1】visitor模式

          最近一直在使用ASM做一些代码监控和AOP拦截的事情,接下来准备对做的这些事情做下记录,以备后用,还是那句话:好记性不入烂键盘。

          存在这样的问题:在一个集合当中,存在着多种不同的类型,但是需求需要对这个集合当中的所有元素遍历之后进行类似逻辑的一个操作。退一步思考,如果集合中都是同类元素,这会很简单,只要在这类元素的对象上执行固定的操作就可以。而现在的情况是多种不同类型的对象,显然没法直接对这些对象进行固定的操作,于是visitor模式被引入进来了。

          在我理解来,visitor模式的意图是:1.把类型和对类型上的操作进行解耦。使用的场景是在一个框架中定义了一系列的类型,框架则给出了对这一系列类型进行操作的骨架算法,而对每一个类型的具体操作是需要提供给用户来定制的(当然框架应该给出每个类型元素的操作的默认行为)。2.基于第一点之上带来的另一个好处:可以在不修改类型的情况下在代码层次上新增对类型的操作。

          visitor模式的结构是:每个类型都实现一个声明了void accept(Visitor visitor)方法的接口,这个接口接受Visitor接口的具体实现类做为入参,但回头又在accept方法的实现中调用visitor对象的visitor方法,并把自身的类型传入其中。这个过程叫做双重分派。实际上,Visitor接口的不同具体实现对应了对类型上不同的操作,具体操作是在visitor方法中完成的,由于visitor方法调用时入参是当前类型的实例,所以对于当前类型的具体操作,是可以由操作和类型共同决定的,只是在结构上把二者拆开了。

          visitor模式的类图如下:

【ASM系列1】visitor模式

          模式中包含的主要角色有:

          抽象节点接口(Element):定义了accept(Visitor v)方法的接口,实现了这个接口的具体节点类可以接受访问者的访问。

          具体节点类(ConcreteElementA、ConcreteElementB等):实现了抽象节点接口的accept方法,方法的实现通常是调用作为入参的访问者对象的对当前节点类型进行访问的的方法,并传入当前节点对象作为入参。这里我们可以可以看到,每个具体的访问者对象,都需要对不同的具体节点类提供一个visitor方法,也就是说,具体节点类有10个,具体访问者的实现中可能就有10个对应的visitor方法(这是访问者模式的一个缺点,后面会提到)。

          抽象访问者接口(Visitor):定义了一系列可以对不同具体节点进行访问的访问方法。访问方法的名字不限,可以如图中所示,对于不同的具体类,命名一个和具体类名相关的访问方法;也可以简单得使用visitor这个名字,那是因为对不同的具体节点进行访问时要接收该具体类的对象做为入参,可以使用visitor方法上的不同入参类型来区分改访问方法是对不同具体节点类的访问方法。

          具体访问者类(ConcreteVisitor):实现了Visitor接口,并提供了接口中定义的对不同节点类的访问方法的实现。

          结构对象(ObjectStructure):是一个容器的角色,它维护了不同类型的具体节点类,并可以对容器的元素进行访问。

          访问者模式的特点:

          根据对扩展开放,对修改关闭的开闭原则。我认为访问者模式的最大特点是便于对现有的数据结构增加操作,而不便于增加新的类型。因为新增操作的改动是增加一个新的具体访问者类(即是对现有应用的扩展),而新增一个类型的改动是要对现有的访问者增加对于新增加的类型的访问方法(修改现有的访问者类)。这是visitor模式最大的优点和最明显的缺点了。

           我对ASM实现的理解:

           ASM使用了树的结构来对复杂的java字节码进行访问,并结合push模型来进行对字节码的修改。以下我结合ASM中的几个重要数据结构来进行理解:

           ClassReader:它将字节数组或者class文件读入到内存当中,并形成内部表示的树结构。

           ClassVisitor:ClassReader对象构建好之后,需要调用这个类的accept方法,这个方法接收ClassVisitor类的实例作为参数。框架在遍历树结构的不同节点时会调用ClassVisitor类上不同的visitor方法,遍历并调用ClassVisitor的算法骨架是由ClassReader确定的,用户可以做的是提供自己的ClassVisitor类的实现,从而实现对字节码的修改。ClassVisitor上的一些访问会产生一些子过程,比如visitMethod会产生MethodVisitor的调用,visitField会产生对FieldVisitor的调用,用户也可以对这些Visitor进行自己的实现,从而达到对这些子节点上的字节码访问的修改。

          用户还可以提供多中不种操作的ClassVisitor实现,并以职责链的模式提供给ClassReader来使用(ASM中的设计模式真多啊,而且设计真心挺牛逼的)。对于ClassReader,依然只需要accept一个ClassVisitor类,因为ClassVisitor同时也是职责链上的元素类型。

          ClassVisitor中包括的visitor方法有:

【ASM系列1】visitor模式

           ClassWriter:生成字节码的工具类,也是ClassVisitor接口的实现,它一般作为职责链上的最后一个节点被执行。即其前面的ClassVisitor链条上的每一个visitor都是致力与对原始字节码做某一项修改,ClassWriter这个visitor的操作则是老实得把每一个节点的字节码输出到指定的文件当中。

          ClassAdapter:它实现了ClassVisitor定义的所有接口,并接受一个ClassVisitor对象作为构建一个新的ClassAdapter实例的参数。所以它的实现一般是职责链上的一个节点。

          ASM的大体实现流程是这样的:ClassReader读取字节码,生成用于表示该字节码的内部表示的树;组装一系列ClassVisitor的链条,这些visitor对应与visitor模式中的具体访问者类,一般都完成了字节码进行一项不同的字节码改写的操作,而整个职责链则完成了对字节码的一系列不同的字节码修改工作;然后调用ClassReader的accept方法,传入ClassVisitor的对象,也是一个职责链条,ClassReader使用这个链条上的每个Visitor对已加载进内存的字节码的树结构上的每个节点进行访问,在链条的末端,调用ClassWriter这个visitor进行修改后的字节码的输出工作。

         对ASM的理解到此结束,that is all,thank you!后续再来介绍asm的具体使用和一些字节码操作上的知识。