南通最专业的开锁公司 南通最好的开锁公司 君威遥控器电池 君威专用汽车钥匙电池雷诺科雷傲汽车钥匙遥控器电池更换方法 南通开锁公司电话_南通修锁 换锁芯 开汽车锁 崇川区 港闸区 如皋 如东开锁 南通开锁公司电话 南通金钥匙开锁服务部-专业开修锁 换锁芯(正规公安工商备案) 南通开锁公司电话 南通开锁公司修锁 装锁电话|崇川区开锁|港闸区开锁 led吸顶灯光源 吸顶灯32w 吸顶灯 灯管 吸顶灯 节能灯泡 led吸顶灯灯泡 三头吸顶灯 led吸顶灯灯管 欧普圆形吸顶灯 55w吸顶灯 吸顶灯光源 旅游壶 真空保温 汉方养生壶 美的电热水壶15s08a2 半球牌电饭煲 美的 mg123-d hd2 飞科 三洋洗衣机全自动 新安怡 学饮杯 黑苦荞黄酮软胶囊 苦荞糖安胶囊 五彩凉山苦荞茶 苦荞茶250g 全胚芽黑苦荞茶500 内蒙特产苦荞茶 富贵康苦荞茶 苦荞面包邮 正中苦荞茶包邮 正中苦荞茶 包邮 苦荞香茶西部 苦荞香茶 西部 轩庆苦荞玫瑰鲜花饼 苦荞真空 苦荞500克 苦荞茶120 苦荞 真空 0.02安全套包邮0.2大力马鱼线0.4mm0.4鱼线0.5l氮纤维高压瓶007sh充电器007电子请柬00高达xn-raiser01102400018401背囊0-25微米千分尺03g101系列全套图集0-3个月婴儿连体哈衣05-08本田奥德赛05ffc23套机05版河南动感地带0603贴片电阻包06年款英派斯06斯巴鲁森林人cd机07军衔07军用水壶07款福克斯两厢07数码迷彩07速腾大灯总成 泰好大门锁 泰好豪华大门锁 cei超b级锁芯 玻璃橱门锁 指纹锁耶鲁4109 耶鲁凯特曼指纹锁 防盗挂锁包邮 包挂锁 包梁挂锁 新君威门锁 新君威门锁扣 指纹锁木门 太空铝黑色门锁 泰好浴室铜门锁 浴室门锁不锈钢 1178冷库门锁 南通配新桑塔纳2000 3000 时代超人 志俊汽车钥匙_配帕桑塔纳钥匙遥控器_帕萨特钥匙全丢重配崇川区 南通配宝来汽车钥匙_配宝来遥控器_宝来钥匙全丢如皋 南通配帕萨特汽车钥匙_配帕萨特遥控器_帕萨特钥匙全丢通州 南通配凯越汽车钥匙_配凯越遥控器_凯越钥匙全丢如东

  • 格子衬衣女 长袖 秋
  • t男恤 短袖 韩版包邮
  • 男士职业衬衫
  • 衬衫 女 长袖点点
  • 衬衫 素色 男 宽松 休闲
  • 职场气质衬衫女
  • t恤男短袖加大码不掉色
  • 帅T 小西装
  • 裤子棕色长裤男
  • 衬衣 女 灯芯绒
  • 衬衣 女商务
  • 女士职业衬衫短袖
  • 休闲裤 男 修身
  • jamesearl纯棉休闲长裤
  • 占姆士女式长袖衬衫
  • 男黑色商务休闲棉长裤
  • 女士长袖衬衫
  • 男士绿色休闲长裤
  • 全棉短袖衬衫 男 薄
  • 女士长袖纯棉衬衫
  • 秋裤男直筒
  • 马自达3汽车挂件吊坠饰品 高档琉璃汽车挂饰品蒙迪欧致胜汽车挂件吊坠饰品 高档琉璃汽车挂饰品 启辰D50汽车挂件吊坠饰品 高档琉璃汽车挂饰品起亚K2汽车挂件吊坠饰品 高档琉璃汽车挂饰品索纳塔汽车挂件吊坠饰品 高档琉璃汽车挂饰品天籁汽车挂件吊坠饰品 高档琉璃汽车挂饰品 天语SX4汽车挂件吊坠饰品 高档琉璃汽车挂饰品英朗GT汽车挂件吊坠饰品 高档琉璃汽车挂饰品英伦SC7汽车挂件吊坠饰品 高档琉璃汽车挂饰品自由舰汽车挂件吊坠饰品 高档琉璃汽车挂饰品雨燕汽车挂件吊坠饰品 高档琉璃汽车挂饰品雪佛兰迈锐宝汽车挂件吊坠饰品 高档琉璃汽车挂饰品

    秋冬季现代瑞纳 宝马5系 帕萨特 日产骐达专用汽车毛绒方向盘套 江苏省盐城市地图2014 最新版 江苏省卫星地图2014 最新版 百度地图-谷歌地图-中国地图-北京地图-搜狗地图google地图 广本凌派汽车座垫 凌派秋冬季汽车坐垫 四季通用 长安福特福克斯油耗详解 宝来汽车遥控钥匙电池 更换方法 图解宝来遥控钥匙的电池换法 宝骏630车钥匙遥控器电池更换步骤 更换方法 迈腾车钥匙电池 迈腾更换遥控器电池详解逍客汽车钥匙电池 遥控器逍客智能钥匙的电池更换步骤 新天籁智能遥控钥匙电池 新天籁智能钥匙如何更换纽扣电池 golf6 汽车钥匙遥控器换电池 图解高尔夫6遥控钥匙的电池换法 长安福特福克斯加速时间详解[图] 福克斯汽车钥匙电池 福克斯钥匙电池更换作业 Q5车钥匙更换电池方法 图解新凯越车钥匙更换电池
  • 汽车坐垫 可爱
  • wrc汽车坐垫
  • 汽车坐垫套四季通用
  • 明锐冰丝 汽车坐套
  • 骐达汽车脚垫全包围
  • 三菱蓝瑟汽车坐垫
  • 捷达汽车脚垫 3d
  • 奥迪S7坐垫
  • 别克荣御坐垫
  • Jeep指南者坐垫
  • 力帆320坐垫
  • 路虎卫士坐垫
  • 双龙爱腾坐垫
  • 斯柯达Yeti坐垫
  • 现代途胜坐垫
  • 一汽威乐坐垫
  • 哈飞路宝汽车坐垫
  • 吉利远景汽车坐垫
  • 吉利全球鹰汽车坐垫
  • 房门锁执手锁 防盗挂锁
  • 运动短裤
  • 短裤 男
  • 半裙
  • 手工拖鞋鞋底
  • iphone4皮套 手机套
  • 机油滤清器扳手工具
  • 桑塔纳3000机油
  • 家居摆件现代
  • 玻璃摆件
  • 瓷器摆件
  • 马到成功摆件
  • 高档瓷器摆件
  • 宝马山地自行车包邮
  • 根雕工艺品摆件
  • 家居摆件吊脚娃娃
  • 泰国佛像摆件工艺品
  • 美利达儿童自行车
  • 天鹅摆件结婚
  • 龙龟摆件纯铜
  • 金摆件聚宝盆
  • 亲子自行车 女式
  • 客厅卧室装饰摆件
  • 贵宾狗摆件
  • 木摆件
  • 羚羊头摆件
  • 招财兔摆件
  • 自行车前包
  • 牛仔短裤
  • 短裤 男 休闲
  • 高腰短裤
  • 夏季短裤
  • 蕾丝短裤
  • 连体短裤
  • 裙裤
  • 裤裙
  • 拖鞋 海外
  • 拖鞋 浴室
  • 拖鞋 防滑
  • 拖 鞋 男
  • 拖鞋 女
  • 浴室拖鞋
  • 家居拖鞋
  • 凉拖鞋
  • 夏天拖鞋
  • 香菇丸
  • 韩国饮料
  • 打草绳小松打草绳二手挖掘机小松 小松割灌机配件 钢木书桌办公桌钢木桌玻璃杯子水壶钢化玻璃水壶钢木办公桌钢木电脑桌进口玻璃水壶玻璃水成份防冻玻璃水包邮防冻玻璃水车用 环保果皮箱环保垃圾袋环保垃圾桶小松120-6常州小松割草机小松打草头小松割灌机小松割灌机配件 咖啡厅员工制服_咖啡厅服务员制服_西餐咖啡服务员制服_咖啡馆制服_咖啡店西餐
  • 5号充电电池8节套装
  • 5-7号电池充电器
  • 充电电池5号正品
  • 可充电电池5号
  • 7号充电电池批发
  • 7号电池包邮
  • 1.5v充电电池5号
  • 5号充电器电池免邮
  • 双鹿电池5号正品
  • 双鹿9v电池
  • 双鹿电池批发
  • 双鹿7号电池
  • 双鹿1号电池
  • 双鹿碱性电池
  • 三星s4电池原装正品
  • 三星s4原装电池后盖
  • n7100原装电池
  • 小米2s原装电池
  • galaxy s4大容量电池
  • galaxy s4原装电池
  • galaxy s3电池
  • galaxy nexus电池
  • 三星galaxy s4电池后盖
  • 品胜小米2s电池
  • 品胜手机电池
  • 小米2a电池品胜
  • 背夹电池iphone4s
  • note2背夹电池
  • 三星s4背夹电池皮套
  • 自行车fixedgear 自行车眼镜 近视 婴儿推车小自行车 自行车骑行眼镜风镜 钻石自行车 山地自行车立管 led自行车气嘴专用灯 三鼎自行车坐椅儿童 自行车线管 金镶玉玉器挂件 自行车脚踏双轴承 铬钼钢架自行车 邦德自行车女式 山地公路自行车 自行车骑行手电筒 自行车灯架 灯夹包邮 自行车水壶支架 自行车 组装 mosso 自行车载mp3 26自行车 男生单车 自行车 折叠 复古 giant山地车 自行车 炫彩自行车 七彩马亲子自行车 ucc山地车 自行车 贝嘉琦 女孩自行车 zxc自行车配件码表 自行车前置安全椅 女童自行车 小龙哈比 自行车碳纤维垫圈 自行车骑行用品 装饰木板板材 美式壁挂装饰 家具装饰配件 衣服装饰亮片 婚礼礼品实用 公司开业庆典礼品 婴儿衣柜 儿童欧式公主床 床围栏护栏嵌入式 床靠背套 派克圆珠笔芯 卧室台灯床头灯现代 个性笔袋 派克im签字笔 彩色铅笔包邮 派克宝珠笔芯 派克笔芯正品 百乐摩可擦笔笔芯 斑马油笔 微型钢笔钓鱼竿 电子教鞭遥控笔 镭射灯激光笔 彩色水笔芯 黑色水笔笔芯 公爵宝珠笔芯 touch3代马克笔 韩国文具笔包邮 水溶性彩色铅笔 马可 sgp触控笔 礼品创意笔筒 铂金 项链 pt950 男 老竹笔筒 竹雕笔筒 六福珠宝铂金手镯 8.9寸笔记本电脑 三菱签字笔1.0 华硕游戏笔记本 插座面板

    使用 ASM 实现 Java 语言的“多重继承”

    问题的提出

    在大部分情况下,需要多重继承往往意味着糟糕的设计。但在处理一些遗留项目的时候,多重继承可能是我们能做出的选择中代价最小的。由于 Java 语言本身不支持多重继承,这常常会给我们带来麻烦,最后的结果可能就是大量的重复代码。本文试图使用 ASM 框架来解决这一问题。在扩展类的功能的同时,不产生任何重复代码。

    考虑如下的实际情况:有一组类,名为 SubClass1、SubClass2、SubClass3 和 SubClass4,它们共同继承了同一个父类 SuperClass。现在,我们需要这组类中的一部分,例如 SubClass1 和 SubClass2,这两个类还要实现另外两个接口,它们分别为:IFibonacciComputer 和 ITimeRetriever。然而,这两个接口已经有了各自的实现类 FibonacciComputer 和 TimeRetriever。并且这两个类的实现逻辑就是我们想要的,我们不想做任何改动,只希望在 SubClass1 和 SubClass2 两个类中包含这些实现逻辑。

    它们的结构如图 1 所示:


    图 1. 结构类图
    图 1. 结构类图

    由于 SubClass1,SubClass2 已经继承了 SuperClass,所以我们无法让它们再继承 FibonacciComputer 或 TimeRetriever。

    所以,想要它们再实现 IFibonacciComputer 和 ITimeRetriever 这两个接口,必然会产生重复代码。

    下面,我们就使用 ASM 来解决这个问题。

    回页首

    Java class 文件格式以及类加载器介绍

    在后面的内容中,需要对 Java class 文件格式以及类加载器的知识有一定的了解,所以这里先对这些内容做一个简单介绍:

    class 文件格式

    Java class 文件的结构如图 2 所示(图中“*”表示出现 0 次或任意多次):


    图 2.Java class 文件结构
    图 2.Java class 文件结构

    详细说明如下:

    • Magic Number: 每个 class 文件的前 4 个字节被称为“魔数”,它的内容为:0xCAFEBABE。魔数的作用在于可以轻松地分辨出一个文件是不是 class 文件。
    • Version: 该项指明该 class 文件的版本号。
    • Constant Pool: 常量池是 class 文件中结构最为复杂,也最为重要的部分。常量池包含了与文件中类和接口相关的常量。常量池中存储了诸如文字字符串,final 变量值。Java 虚拟机把常量池组织为入口列表的形式。常量池中许多入口都指向其他的常量入口,而且 class 文件中紧随着常量池的许多条目也都会指向常量池的入口。除了字面常量之外,常量池还可以容纳以下几种符号引用:类和接口的全限定名,字段的名称和描述符和 方法的名称和描述符等。
    • Modifiers: 该项指明该文件中定义的是类还是接口,以及声明中用了哪种修饰符,类或接口是私有的,还是公共的,类的类型是否是 final 的,等等。
    • This class: 该项是对常量池的索引。在这个位置,Java 虚拟机能够找到一个容纳了类或接口全限定名的入口。这里需要注意的是:在 class 文件中,所有类的全限定名都是以内部名称形式表示的。内部名称是将原先类全限定名中的“.”替换为“/”。例如:java.lang.String 的内部名称为 java/lang/String。
    • Super Class: 该项也是对常量池的索引,指明了该类超类的内部名称。
    • Interfaces: 该项指明了由该类直接实现或由接口扩展的父接口的信息。

    :Modifiers,This Class,Super Class 和 Interfaces 这四项的和就是一个类的声明部分。

    • Annotation: 该项存储的是注解相关的内容,注解可能是关于类的,方法的以及字段的。
    • Attribute: 该项用来存储关于类,字段以及方法的附加信息。在 Java 5 引入了注解之后,该部分内容几乎已经没有用处。
    • Field: 该项用来存储类的字段信息。
    • Method: 该项用来存储类的方法信息。

    类装载器介绍

    类装载器负责查找并装载类。每个类在被使用之前,都必须先通过类装载器装载到 Java 虚拟机当中。Java 虚拟机有两种类装载器 :

    • 启动类装载器

      启动类装载器是 Java 虚拟机实现的一部分,每个 Java 虚拟机都必须有一个启动类装载器,它知道怎么装载受信任的类,比如 Java API 的 class 文件。

    • 用户自定义装载器

      用户自定义装载器是普通的 Java 对象,它的类必须派生自 java.lang.ClassLoader 类。ClassLoader 类中定义的方法为程序提供了访问类装载机制的接口。

    回页首

    ASM 简介以及编程模型

    ASM 简介

    ASM 是一个可以用来生成\转换和分析 Java 字节码的代码库。其他类似的工具还有 cglibserpBCEL等。相较于这些工具,ASM 有以下的优点 :

    • ASM 具有简单、设计良好的 API,这些 API 易于使用。
    • ASM 有非常良好的开发文档,以及可以帮助简化开发的 Eclipse 插件
    • ASM 支持 Java 6
    • ASM 很小、很快、很健壮
    • ASM 有很大的用户群,可以帮助新手解决开发过程中遇到的问题
    • ASM 的开源许可可以让你几乎以任何方式使用它

    编程模型

    ASM 提供了两种编程模型:

    • Core API,提供了基于事件形式的编程模型。该模型不需要一次性将整个类的结构读取到内存中,因此这种方式更快,需要更少的内存。但这种编程方式难度较大。
    • Tree API,提供了基于树形的编程模型。该模型需要一次性将一个类的完整结构全部读取到内存当中,所以这种方法需要更多的内存。这种编程方式较简单。

    下文中,我们将只使用 Core API,因此我们只介绍与其相关的类。

    Core API 中操纵字节码的功能基于 ClassVisitor 接口。这个接口中的每个方法对应了 class 文件中的每一项。Class 文件中的简单项的访问使用一个单独的方法,方法参数描述了这个项的内容。而那些具有任意长度和复杂度的项,使用另外一类方法,这类方法会返回一个辅助的 Visitor 接口,通过这些辅助接口的对象来完成具体内容的访问。例如 visitField 方法和 visitMethod 方法,分别返回 FieldVisitor 和 MethodVisitor 接口的对象。

    清单 1 为 ClassVisitor 中的方法列表:


    清单 1.ClassVisitor 接口中的方法
    				  public interface ClassVisitor {     // 访问类的声明部分     void visit(int version, int access, String name, String   signature,String superName, String[] interfaces);     // 访问类的代码     void visitSource(String source, String debug);     // 访问类的外部类     void visitOuterClass(String owner, String name, String desc);     // 访问类的注解     AnnotationVisitor visitAnnotation(String desc, boolean visible);     // 访问类的属性     void visitAttribute(Attribute attr);     // 访问类的内部类     void visitInnerClass(String name, String outerName,   String innerName,int access);     // 访问类的字段     FieldVisitor visitField(int access, String name, String desc,   String signature, Object value);     // 访问类的方法     MethodVisitor visitMethod(int access, String name,   String desc,String signature, String[] exceptions);     // 访问结束     void visitEnd();   }  

    ClassVisitor 接口中的方法在被调用的时候是有严格顺序的,其顺序如清单 2 所示(其中“?”表示被调用 0 次或 1 次。“*”表示被调用 0 次或任意多次):


    清单 2.ClassVisitor 中方法调用的顺序
    				   visit   visitSource?    visitOuterClass?    ( visitAnnotation| visitAttribute)*    ( visitInnerClass| visitField| visitMethod)*    visitEnd 			

    这就是说,visit 方法必须最先被调用,然后是最多调用一次 visitSource 方法,然后是最多调用一次 visitOuterClass 方法。然后是 visitAnnotation 和 visitAttribute 方法以任意顺序被调用任意多次。再然后是以任任意顺序调用 visitInnerClass ,visitField 或 visitMethod 方法任意多次。最终,调用一次 visitEnd 方法。

    ASM 提供了三个基于 ClassVisitor 接口的类来实现 class 文件的生成和转换:

    • ClassReader:ClassReader 解析一个类的 class 字节码,该类的 accept 方法接受一个 ClassVisitor 的对象,在 accept 方法中,会按上文描述的顺序逐个调用 ClassVisitor 对象的方法。它可以被看做事件的生产者。
    • ClassAdapter:ClassAdapter 是 ClassVisitor 的实现类。它的构造方法中需要一个 ClassVisitor 对象,并保存为字段 protected ClassVisitor cv。在它的实现中,每个方法都是原封不动的直接调用 cv 的对应方法,并传递同样的参数。可以通过继承 ClassAdapter 并修改其中的部分方法达到过滤的作用。它可以看做是事件的过滤器。
    • ClassWriter:ClassWriter 也是 ClassVisitor 的实现类。ClassWriter 可以用来以二进制的方式创建一个类的字节码。对于 ClassWriter 的每个方法的调用会创建类的相应部分。例如:调用 visit 方法就是创建一个类的声明部分,每调用一次 visitMethod 方法就会在这个类中创建一个新的方法。在调用 visitEnd 方法后即表明该类的创建已经完成。它最终生成一个字节数组,这个字节数组中包含了一个类的 class 文件的完整字节码内容 。可以通过 toByteArray 方法获取生成的字节数组。ClassWriter 可以看做事件的消费者。

    通常情况下,它们是组合起来使用的。

    下面举一个简单的例子:假设现在需要对 class 文件的版本号进行修改,将其改为 Java 1.5。操作方法如下:

    1. 首先通过 ClassReader 读取这个类;
    2. 然后创建一个 ClassAdapter 的子类 ChangeVersionAdapter。在 ChangeVersionAdapter 中,覆盖 visit 方法,在调用 ClassVisitor#visit 方法时修改其中关于版本号的参数。该方法的签名如下:visit(int version, int access, String name, String signature, String superName, String[] interfaces),其中每个参数的含义如下:
      1. version:class 文件的版本号,这就是我们需要修改的内容;
      2. access:该类的访问级别;
      3. name:该类的内部名称;
      4. signature:该类的签名,如果该类与泛型无关,这个参数就是 null;
      5. superName:父类的内部名称;
      6. interfaces:该类实现的接口的内部名称;

    明白这些参数的含义之后,修改就很容易,只需要在调用 cv.visit 的时候,将 version 参数指定为 Opcodes.V1_5 即可(Opcodes 是 ASM 中的一个类),其他参数不加修改原样传递。这样,经过该 ClassAdapter 过滤后的类的版本号就都是 Java 1.5 了。

    1. 在创建 ChangeVersionAdapter 对象时,构造方法传递一个 ClassWriter 的对象。该 ClassWriter 会随着 ChangeVersionAdapter 方法的调用按顺序创建出类的每一个部分。由于在 visit 方法中,version 参数已经被过滤为 Opcodes.V1_5,所以该 ClassWriter 最终产生的 class 字节码的版本号就是 V1.5 的;
    2. 然后通过 ClassWriter#toByteArray 方法获取修改后的类的完整的字节码内容;
    3. 当然,想要使用这个类,还需要通过一个自定义类加载器,将获得到的字节码加载到虚拟机当中,才可以创建这个类的实例;

    代码片段如清单 3 所示:


    清单 3. 使用 ASM 的代码示例
    				 …  // 使用 ChangeVersionAdapter 修改 class 文件的版本  ClassReader cr = new ClassReader(className);   ClasssWriter cw = new ClassWriter(0);   // ChangeVersionAdapter 类是我们自定义用来修改 class 文件版本号的类  ClassAdapter ca = new ChangeVersionAdapter (cw);   cr.accept(ca, 0);   byte[] b2 = cw.toByteArray();  … 

    图 3 是相应的 Sequence 图:


    图 3. 修改版本号的 Sequence 图
    图 3. 修改版本号的 Sequence 图

    代码示例

    在了解了以上的知识之后再回到我们刚开始提出的问题中,我们希望 SubClass1 和 SubClass2 在继承自 SuperClass 的同时还要实现 IFibonacciComputer 以及 ITimeRetriever 两个接口。

    为了后文描述方便,这里先确定三个名词:

    • 实现类即完成了接口实现的类,这里为 FibonacciComputer 以及 TimeRetriever。
    • 待增强类即需要实现功能增强,加入实现逻辑的类,这里为 SubClass1 和 SubClass2。
    • 增强类即在待增强类的基础上,加入了接口实现的类。这个类目前没有实际的类与之对应。

    如果只能在源代码级别进行修改,我们能做的仅仅是将实现类的代码拷贝进待增强类。(当然,有稍微好一点的做法在每一个待增强类中包含一个实现类,以组合的方式实现接口。但这仍然不能避免多个待增强类中的代码重复。)

    在学习了 ASM 之后,我们可以直接从字节码的层次来进行修改。回忆一下上文中的内容:使用 ClassWrite 可以直接创建类的字节码,不同的方法创建了 class 文件的不同部分,尤其重要的是以下几个方法:

    • visit 方法创建一个类的声明部分
    • visitField 方法创建一个类的字段
    • visitMethod 方法创建一个类的方法
    • visitEnd 方法,表明该类已经创建完成

    所以,现在我们可以直接从字节码的层次完成这一需求:动态的创建一个新的类(即增强类)继承自待增强类,同时在该类中,将实现类的实现方法添加进来

    完整的实现逻辑如下:

    1. 创建一个 ClassAdapter 的子类 AddImplementClassAdapter,这个类将被用来访问待增强类。AddImplementClassAdapter 期待一个 ClassWriter 作为 ClassVisitor。该 ClassWriter 在访问待增强类的同时逐步完成增强类的创建。
    2. 在 AddImplementClassAdapter 中覆盖 visitEnd 方法,在调用 ClassVisitor#visitEnd 方法之前,使用该 ClassVisitor 逐个访问每一个实现类。由于实现类中的内容也需要进行一定的过滤,所以这里的 ClassVisitor 在访问实现类的时候也需要经过一个 ClassAdapter 进行过滤。创建另一个 ClassAdapter 的子类 ImplementClassAdapter 来完成这个过滤。由于这个 ClassVisitor 是一个 ClassWriter。这样做的效果就是将实现类的内容添加到增强类中。
    3. 在完成了所有实现类的访问之后,调用 ClassVisitor#visitEnd 方法表明增强类已经创建完成。
    4. 使用一个 ClassReader 的对象读取待增强类。
    5. 创建一个 AddImplementClassAdapter 的实例,同时提供一个 ClassWriter 作为 ClassVisitor。
    6. 通过 accept 方法将前面创建的 AddImplementClassAdapter 对象传递给这个 ClassReader 对象。
    7. 在访问完待增强类之后,ClassWriter 即完成了增强类的创建,所以最后通过 toByteArray 方法获取这个增强类的字节码。
    8. 最后通过一个自定义类加载器将其加载到虚拟机当中。

    下面是代码示例与讲解:

    首先需要修改 SubClass1 以及 SubClass2 两个类,使其声明实现 IFibonacciComputer 和 ITimeRetriever 这两个接口。由于这两个类并没有真正的包含实现接口的代码,所以它们现在必须标记为抽象类。修改后的类结构如图 4 所示:


    图 4. 修改后的类图
    图 4. 修改后的类图

    然后创建以下几个类:

    • AddImplementClassAdapter: 过滤待增强类,并引导 ClassWriter 创建增强类的适配器。
    • ImplementClassAdapter: 实现类的适配器,过滤多余内容,将实现类中的内容通过 ClassWriter 添加到增强类中。
    • ModifyInitMethodAdapter: 修改待增强类构造方法的适配器。
    • SimpleClassLoader: 自定义类加载器,用来加载动态生成的增强类。
    • EnhanceFactory: 提供对外接口,方便使用。
    • EnhanceException: 对异常的统一包装,方便异常处理。

    下面,我们来逐一实现这些类:

    AddImplementClassAdapter: 首先在构造方法中,我们需要记录下待增强类的 Class 对象,增强类的类名,实现类的 Class 对象,以及一个 ClassWriter 对象,该构造方法代码如清单 4 所示:


    清单 4.AddImpelementClassAdapter 构造方法代码
    				  public AddImplementClassAdapter( ClassWriter writer,   String enhancedClassName,Class<?> targetClass,   Class<?>... implementClasses) {      super(writer);      this.classWriter = writer;      this.implementClasses = implementClasses;      this.originalClassName = targetClass.getName();      this.enhancedClassName = enhancedClassName;   }  

    在 visit 方法中需要完成增强类声明部分的创建,增强类继承自待增强类。该方法代码如清单 6 所示:


    清单 5.visit 方法代码
    // 通过 visit 方法完成增强类的声明部分			  public void visit(int version, int access, String name,   String signature,String superName, String[] interfaces) {        cv.visit(version, Opcodes.ACC_PUBLIC,       // 将 Java 代码中类的名称替换为虚拟机中使用的形式       enhancedClassName.replace('.', '/'),        signature, name,interfaces);   }  

    visitMethod 方法中需要对构造方法做单独处理,因为 class 文件中的构造方法与源代码中的构造方法有三点不一样的地方:

    1. 每个 class 文件中至少有一个构造方法。即便你在类的源代码中没有编写构造方法,编译器也会为你生成一个默认构造方法 ;
    2. 在 class 文件中与源代码中的构造方法名称不一样,class 文件的构造方法名称都为“<init>”;
    3. class 文件中每个构造方法都会最先调用一次父类的构造方法。

    鉴于这些原因,增强类的构造方法需要在待增强类构造方法的基础上进行修改。修改的内容就是对于父构造方法的调用,因为增强类和待增强类的父类是不一样的。

    visitMethod 方法中需要判断如果是构造方法就通过 ModifyInitMethodAdapter 修改构造方法。其他方法直接返回 null 丢弃(因为增强类已经从待增强类中继承了这些方法,所以这些方法不需要在增强类中再出现一遍),该方法代码如清单 7 所示:


    清单 6.visitMethod 方法代码
    				  public MethodVisitor visitMethod(int access, String name,   String desc,String signature, String[] exceptions) {      if (INTERNAL_INIT_METHOD_NAME.equals(name)) {         // 通过 ModifyInitMethodAdapter 修改构造方法         MethodVisitor mv = classWriter.visitMethod(access,          INTERNAL_INIT_METHOD_NAME, desc, signature, exceptions);          return new ModifyInitMethodAdapter(mv, originalClassName);      }      return null;   }  

    最后,在 visitEnd 方法,使用 ImplementClassAdapter 与 ClassWriter 将实现类的内容添加到增强类中,最后再调用 visitEnd 方法表明增强类已经创建完成:


    清单 7.visitEnd 方法代码
    				  public void visitEnd() {   for (Class<?> clazz : implementClasses) {      try {        // 逐个将实现类的内容添加到增强类中。       ClassReader reader = new ClassReader(clazz.getName());        ClassAdapter adapter =        new ImplementClassAdapter(classWriter);        reader.accept(adapter, 0);      } catch (IOException e) {          e.printStackTrace();      }   }   cv.visitEnd();   }  

    ImplementClassAdapter:该类对实现类进行过滤。

    首先在 visit 方法中给于空实现将类的声明部分过滤掉,代码如清单 8 所示:


    清单 8.visit 方法代码
    				  public void visit(int version, int access, String name,   String signature,String superName, String[] interfaces) {      // 空实现,将该部分内容过滤掉  }   

    然后在 visitMethod 中,将构造方法过滤掉,对于其他方法,调用 ClassVisitor#visitMethod 进行访问。由于这里的 ClassVisitor 是一个 ClassWriter,这就相当于在增强类中创建了该方法,代码如清单 9 所示:


    清单 9.visitMethod 方法代码
    				  public MethodVisitor visitMethod(int access, String name,   String desc,String signature, String[] exceptions) {      // 过滤掉实现类中的构造方法     if (AddImplementClassAdapter.INTERNAL_INIT_METHOD_NAME.equals(name)){          return null;      }      // 其他方法原样保留     return cv.visitMethod(access, name, desc, signature, exceptions);   }  

    ModifyInitMethodAdapter:上文中已经提到,ModifyInitMethodAdapter 是用来对增强类的构造方法进行修改的。MethodAdapter 中的 visitMethodInsn 是对方法调用指令的访问。该方法的参数含义如下:

    • opcode 为调用方法的 JVM 指令,
    • owner 为被调用方法的类名,
    • name 为方法名,
    • desc 为方法描述。

    所以,我们需要将对于待增强类父类构造方法的调用改为对于待增强类构造方法的调用(因为增强类的父类就是待增强类),其代码如清单 10 所示:


    清单 10. ModifyInitMethodAdapter 类代码
    				 	  /**  	     专门用来修改构造方法的方法适配器 	  */   public class ModifyInitMethodAdapter extends MethodAdapter {   private String className;   public ModifyInitMethodAdapter(MethodVisitor mv, String name) {      super(mv);      this.className = name;   }   public void visitMethodInsn(int opcode, String owner,   String name,String desc) {      // 将 Java 代码中的类全限定名替换为虚拟机中使用的形式     if (name.equals(AddImplementClassAdapter.INTERNAL_INIT_METHOD_NAME)) {      mv.visitMethodInsn(opcode, className.replace(".", "/"),      name, desc);   }   }   }  

    SimpleClassLoader:该自定义类装载器通过提供一个 defineClass 方法来装载动态生成的增强类。方法的实现是直接调用父类的 defineClass 方法,其代码如清单 11 所示:


    清单 11. SimpleClassLoader 类代码
    				  public class SimpleClassLoader extends ClassLoader {   public Class<?> defineClass(String className, byte[] byteCodes) {          // 直接通过父类的 defineClass 方法加载类的结构     return super.defineClass(className, byteCodes,      0, byteCodes.length);   }   }  

    EnhanceException:这是一个异常包装类,其中包含了待增强类和实现类的信息,其逻辑很简单,代码如清单 12 所示:


    清单 12. EnhanceException 类代码
    				 /**   异常类 */   public class EnhanceException extends Exception {   private Class<?> enhanceClass;   private Class<?> [] implementClasses;   // 异常类构造方法  public EnhanceException(Exception ex,Class<?> ec,Class<?>... imClazz){      super(ex);      this.enhanceClass = ec;      this.implementClasses = imClazz;   }   public Class<?> getEnhanceClass() {      return enhanceClass;   }   public Class<?>[] getImplementClasses() {      return implementClasses;   }   }  

    EnhanceFactory:最后,通过 EnhanceFactory 提供对外调用接口,调用接口有两个:

    • public static <T> Class<T> addImplementation(
      Class<T> clazz,Class<?>... implementClasses)
    • public static <T> T newInstance(Class<T> clazz,
      Class<?>... impls)

    为了方便使用,这两个方法都使用了泛型。它们的参数是一样的:第一个参数都是待增强类的 Class 对象,后面是任意多个实现类的 Class 对象,返回的类型和待增强类一致,用户在获取返回值之后不需要进行任何类型转换即可使用。

    第一个方法创建出增强类的 Class 对象,并通过自定义类加载器加载,其代码如清单 13 所示:


    清单 13. addImplementation 方法代码
    				 /**  静态工具方法,在待增强类中加入实现类的内容,返回增强类。 */   public static <T> Class<T> addImplementation(Class<T> clazz,   Class<?>... implementClasses) throws EnhanceException {      String enhancedClassName = clazz.getName() + ENHANCED;      try {         // 尝试加载增强类         return  (Class<T>) classLoader.loadClass(enhancedClassName);          }         // 如果没有找到增强类,则尝试直接在内存中构建出增强类的结构     catch (ClassNotFoundException classNotFoundException) {          ClassReader reader = null;          try {              reader = new ClassReader(clazz.getName());          } catch (IOException ioexception) {              throw new EnhanceException(ioexception,              clazz, implementClasses);          }          ClassWriter writer = new ClassWriter(0);         // 通过 AddImplementClassAdapter 完成实现类内容的织入         ClassVisitor visitor = new AddImplementClassAdapter(          enhancedClassName, clazz, writer, implementClasses);          reader.accept(visitor, 0);          byte[] byteCodes = writer.toByteArray();          Class<T> result = (Class<T>) classLoader.defineClass(          enhancedClassName, byteCodes);          return result;      }   }  

    第二个方法先调用前一个方法,获取 增强类Class对象,然后使用反射创建实例,其代码如清单 14 所示:


    清单 14. newInstance 方法代码
    				 /**  通过待增强类和实现类,得到增强类的实例对象 */   public static <T> T newInstance(Class<T> clazz, Class<?>... impls)   throws EnhanceException {   Class<T> c = addImplementation(clazz, impls);   if (c == null) {      return null;   }   try {         // 通过反射创建实例     return c.newInstance();   } catch (InstantiationException e) {      throw new EnhanceException(e, clazz, impls);   } catch (IllegalAccessException e) {      throw new EnhanceException(e, clazz, impls);   }   }  

    下面是测试代码,先通过 EnhanceFactory 创建增强类的实例,然后就可以像普通对象一样的使用,代码如清单 15 所示:


    清单 15. 使用演示代码
    				  // 不用 new 关键字,而使用 EnhanceFactory.newInstance 创建增强类的实例  SubClass1 obj1 = EnhanceFactory.newInstance(SubClass1.class,   TimeRetriever.class,FibonacciComputer.class);   // 调用待增强类中的方法  obj1.methodInSuperClass();   obj1.methodDefinedInSubClass1();   // 调用实现类中的方法  System.out.println("The Fibonacci number of 10 is "+obj1.compute(10));   System.out.println("Now is :"+obj1.tellMeTheTime());   System.out.println("--------------------------------------");   // 对于 SubClass2 的增强类实例的创建也是一样的  SubClass2 obj2 = EnhanceFactory.newInstance(SubClass2.class,   TimeRetriever.class,FibonacciComputer.class);   // 调用待增强类中的方法  obj2.methodInSuperClass();   obj2.methodDefinedInSubClass2();   // 调用实现类中的方法  System.out.println("The Fibonacci number of 10 is "+obj1.compute(10));   System.out.println("Now is :"+obj1.tellMeTheTime());  

    这里,我们演示了使用 ASM 创建一个新的类,并且修改该类中的内容的方法。而这一切都是在运行的环境中动态生成的,这一点相较于源代码级别的实现有以下的好处:

    • 没有重复代码 这是我们的主要目的,由于增强类是在运行的环境中生成的,并且动态的包含了实现类中的内容,所以,不会产生任何重复代码。
    • 灵活性 使用 EnhanceFactory# addImplementation 方法,对于接口的实现完全是在运行时确定的,因此可以灵活的选择。
    • 可复用性 EnhanceFactory# addImplementation 是一个可以完全复用的方法,我们可以在任何需要的地方使用它。

    需要注意的是,这里我们并没有真正的实现“多重继承”,由于 class 文件格式的限制,我们也不可能真正实现“多重继承”,我们只是在一个类中包含了多个实现类的内容而已。但是,如果你使用增强类的实例通过 instanceof 之类的方法来判断它是否是实现类的实例的时候,你会得到 false,因为增强类并没有真正的继承自实现类。

    另外,为了让演示代码足够的简单,对于这个功能的实现还存在一些问题,例如:

    • FibonacciComputer 和 TimeRetriever 这两个类中,可能会包含一些其他方法,这些方法并非是为了实现接口的方法,而这些方法也会被增强类所包含。
    • 如果多个实现类与待增强类中包含了同样签名的方法时,在创建增强类的过程中会产生异常,因为一个类中不能包含同样方法签名的两个方法。
    • 如果实现类中包含了一些字段,并且实现类的构造方法中初始化了这些字段。但增强类中的这些字段并没有被初始化。因为实现类的构造方法被忽略了。
    • 无法对同一个类做多次不同类型的增强。

    不过,在理解了上文中的知识之后,这些问题也都是可以解决的。

    作为一个可以操作字节码的工具而言,ASM 的功能远不止于此。它还可以用来实现 AOP,实现性能监测,方法调用统计等功能。通过 Google,可以很容易的找到这类文章。

    示例代码包含在 ASM_Demo.zip 中,该文件中包含了上文中提到的所有代码。

    该 zip 文件为 eclipse 项目的归档文件。可以通过 Eclipse 菜单导入至 Eclipse 中,导入方法:File -> Import … -> Existing Projects into Workspace, 然后选择该 zip 文件即可。

    想要编译该项目,还需要 ASM 框架的 jar 包。请在以下地址下载 ASM 框架:http://forge.ow2.org/projects/asm/

    目前该框架正式版的版本号为:3.3.1。

    下载该框架归档文件后解压缩, 并通过 eclipse 将 asm-all-3.3.1.jar(可能是其他版本号)添加到项目编译的类路径中即可。

    代码中包含的 Main 类,是一个包含了 main 方法的可执行类,在 eclipse 中运行该类即可看到运行结果。

    posted @ 2012-01-07 21:39  ChaunceyHao  阅读(418)  评论(0编辑  收藏  举报
  • 汽车羊毛坐垫长毛
  • 车垫四季通用
  • 御车宝
  • 凯美瑞坐垫四季通用
  • 手编冰丝坐垫
  • 珍珠戒指新款
  • 汽车皮坐垫四季垫
  • 汽车四季坐垫新款
  • 汽车皮坐垫四季通用
  • 汽车羊毛坐垫短毛
  • 包女
  • 汽车亚麻坐垫包邮
  • 汽车布坐垫
  • 汽车布坐垫套
  • 紫风铃亚麻四季坐垫
  • 蒙奇奇汽车
  • 蒙奇奇汽车毛绒坐垫
  • 蒙奇奇坐垫三件套
  • 尼罗河车坐垫
  • 尼罗河手编坐垫
  • 尼罗河四季垫
  • 羊剪绒坐垫
  • 牧宝冬季坐垫
  • 牧宝四季垫正品
  • 牧宝羽绒坐垫
  • 牧宝汽车垫羊毛
  • 高压洗车机
  • 高压洗车器
  • 高压洗车水泵
  • 高压洗车泵
  • 熊猫洗车
  • 洗车器高压220v
  • 洗车器便携高压
  • 高压水枪洗车机
  • 220v高压水枪洗车机
  • 家用洗车水枪高压枪
  • 汽车洗车水枪
  • 虎贝尔洗车水枪
  • 水枪头洗车
  • 洗车水枪水管
  • 洗车水枪套装
  • 洗车水枪接头
  • 佳百丽洗车水枪
  • 洗车工具套装
  • 洗车用品工具
  • 家用洗车工具
  • 汽车洗车工具
  • 洗车设备工具
  • 洗车刷子
  • 现代途胜保险杠
  • 现代新胜达保险杠
  • 保险杠车贴纸
  • 后保险杠车贴纸
  • 前保险杠车贴
  • 雨燕保险杠
  • 后保险杠贴纸
  • 前保险杠贴纸
  • 汽车保险贴纸
  • rav4后保险杠
  • 专业运动文胸htc c715e evo design 4g
  • htc evo 4g a9292
  • htc x515m g17 evo 3d
  • htc one x s720e
  • htc x920e
  • 名牌手表男正品
  • povos 奔腾 pffn3003苏泊尔电热水壶正品
  • tcl tcl电热水壶ta-kb181a
  • 乔邦181j
  • 乔邦不锈钢电热水壶
  • supor 苏泊尔 swf15j3-150
  • 逸动空气滤芯
  • 空气压缩机喷漆
  • 德国车载空气净化器
  • sr927w手表电池
  • cartier手表蓝气球
  • 机械手表男士名表
  • anne klein手表
  • 手表柜台展示柜
  • 女士皮带 头层牛皮
  • 女士皮带 头层纯牛皮
  • jeep皮带 纯牛皮
  • 女皮带 真皮 牛皮
  • 女士皮带 真皮
  • 头层牛皮皮带
  • 女士皮带 时尚
  • 真皮皮带头层牛皮女
  • 女士皮带 包邮
  • 皮带条 头层牛皮
  • 女士皮带 时尚 宽
  • 头层牛皮纯铜扣皮带
  • 女士皮带 纯牛皮
  • 女士皮带头层纯牛皮
  • 女士皮带头层牛皮
  • jeep皮带纯牛皮
  • 女皮带真皮牛皮
  • 女士皮带真皮
  • 女士牛皮皮带包邮
  • 女士皮带时尚
  • 三星微单镜头
  • 三星单反镜头
  • 三星50-200镜头
  • 三星7100镜头保护膜
  • 三星i9210
  • 三星i9210手机套面包蛋糕酸奶机
  • 鸡蛋牛奶面包干饼干
  • 面包片饼干
  • 摩托车机油