Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

下面通过Javassist创建上述的Programmer类:

  1. import javassist.ClassPool;
  2. import javassist.CtClass;
  3. import javassist.CtMethod;
  4. import javassist.CtNewMethod;
  5. public class MyGenerator {
  6. public static void main(String[] args) throws Exception {
  7. ClassPool pool = ClassPool.getDefault();
  8. //创建Programmer类
  9. CtClass cc= pool.makeClass("com.samples.Programmer");
  10. //定义code方法
  11. CtMethod method = CtNewMethod.make("public void code(){}", cc);
  12. //插入方法代码
  13. method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
  14. cc.addMethod(method);
  15. //保存生成的字节码
  16. cc.writeFile("d://temp");
  17. }
  18. }

通过JD-gui反编译工具打开Programmer.class 可以看到以下代码:
Mybatis之Javassist字节码生成开源框架 - 图1

代理的基本构成:

代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口;RealSubject角色用来真正完成业务服务功能;Proxy角色负责将自身的Request请求,调用realsubject 对应的request功能来实现业务功能,自己不真正做业务。
Mybatis之Javassist字节码生成开源框架 - 图2
上面的这幅代理结构图是典型的静态的代理模式:

  1. 当在代码阶段规定这种代理关系,Proxy类通过编译器编译成class文件,当系统运行时,此class已经存在了。这种静态的代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于ProxyRealSubject的功能 本质上是相同的,Proxy只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。




为了解决这个问题,就有了动态地创建Proxy的想法:在运行状态中,需要代理的地方,根据Subject 和RealSubject,动态地创建一个Proxy,用完之后,就会销毁,这样就可以避免了Proxy 角色的class在系统中冗杂的问题了。

下面以一个代理模式实例阐述这一问题:

将车站的售票服务抽象出一个接口TicketService,包含问询,卖票,退票功能,车站类Station实现了TicketService接口,车票代售点StationProxy则实现了代理角色的功能,类图如下所示。
Mybatis之Javassist字节码生成开源框架 - 图3
对应的静态的代理模式代码如下所示:

  1. package com.foo.proxy;
  2. /**
  3. * 售票服务接口实现类,车站
  4. * @author louluan
  5. */
  6. public class Station implements TicketService {
  7. @Override
  8. public void sellTicket() {
  9. System.out.println("\n\t售票.....\n");
  10. }
  11. @Override
  12. public void inquire() {
  13. System.out.println("\n\t问询。。。。\n");
  14. }
  15. @Override
  16. public void withdraw() {
  17. System.out.println("\n\t退票......\n");
  18. }
  19. }
  1. package com.foo.proxy;
  2. /**
  3. * 售票服务接口
  4. * @author louluan
  5. */
  6. public interface TicketService {
  7. //售票
  8. public void sellTicket();
  9. //问询
  10. public void inquire();
  11. //退票
  12. public void withdraw();
  13. }
  1. package com.foo.proxy;
  2. /**
  3. * 车票代售点
  4. * @author louluan
  5. *
  6. */
  7. public class StationProxy implements TicketService {
  8. private Station station;
  9. public StationProxy(Station station){
  10. this.station = station;
  11. }
  12. @Override
  13. public void sellTicket() {
  14. // 1.做真正业务前,提示信息
  15. this.showAlertInfo("××××您正在使用车票代售点进行购票,每张票将会收取5元手续费!××××");
  16. // 2.调用真实业务逻辑
  17. station.sellTicket();
  18. // 3.后处理
  19. this.takeHandlingFee();
  20. this.showAlertInfo("××××欢迎您的光临,再见!××××\n");
  21. }
  22. @Override
  23. public void inquire() {
  24. // 1做真正业务前,提示信息
  25. this.showAlertInfo("××××欢迎光临本代售点,问询服务不会收取任何费用,本问询信息仅供参考,具体信息以车站真实数据为准!××××");
  26. // 2.调用真实逻辑
  27. station.inquire();
  28. // 3。后处理
  29. this.showAlertInfo("××××欢迎您的光临,再见!××××\n");
  30. }
  31. @Override
  32. public void withdraw() {
  33. // 1。真正业务前处理
  34. this.showAlertInfo("××××欢迎光临本代售点,退票除了扣除票额的20%外,本代理处额外加收2元手续费!××××");
  35. // 2.调用真正业务逻辑
  36. station.withdraw();
  37. // 3.后处理
  38. this.takeHandlingFee();
  39. }
  40. /*
  41. * 展示额外信息
  42. */
  43. private void showAlertInfo(String info) {
  44. System.out.println(info);
  45. }
  46. /*
  47. * 收取手续费
  48. */
  49. private void takeHandlingFee() {
  50. System.out.println("收取手续费,打印发票。。。。。\n");
  51. }
  52. }

由于我们现在不希望静态地有StationProxy类存在,希望在代码中,动态生成器二进制代码,加载进来。为此,使用Javassist开源框架,在代码中动态地生成StationProxy的字节码:

  1. package com.foo.proxy;
  2. import java.lang.reflect.Constructor;
  3. import javassist.*;
  4. public class Test {
  5. public static void main(String[] args) throws Exception {
  6. createProxy();
  7. }
  8. /*
  9. * 手动创建字节码
  10. */
  11. private static void createProxy() throws Exception
  12. {
  13. ClassPool pool = ClassPool.getDefault();
  14. CtClass cc = pool.makeClass("com.foo.proxy.StationProxy");
  15. //设置接口
  16. CtClass interface1 = pool.get("com.foo.proxy.TicketService");
  17. cc.setInterfaces(new CtClass[]{interface1});
  18. //设置Field
  19. CtField field = CtField.make("private com.foo.proxy.Station station;", cc);
  20. cc.addField(field);
  21. CtClass stationClass = pool.get("com.foo.proxy.Station");
  22. CtClass[] arrays = new CtClass[]{stationClass};
  23. CtConstructor ctc = CtNewConstructor.make(arrays,null,CtNewConstructor.PASS_NONE,null,null, cc);
  24. //设置构造函数内部信息
  25. ctc.setBody("{this.station=$1;}");
  26. cc.addConstructor(ctc);
  27. //创建收取手续 takeHandlingFee方法
  28. CtMethod takeHandlingFee = CtMethod.make("private void takeHandlingFee() {}", cc);
  29. takeHandlingFee.setBody("System.out.println(\"收取手续费,打印发票。。。。。\");");
  30. cc.addMethod(takeHandlingFee);
  31. //创建showAlertInfo 方法
  32. CtMethod showInfo = CtMethod.make("private void showAlertInfo(String info) {}", cc);
  33. showInfo.setBody("System.out.println($1);");
  34. cc.addMethod(showInfo);
  35. //sellTicket
  36. CtMethod sellTicket = CtMethod.make("public void sellTicket(){}", cc);
  37. sellTicket.setBody("{this.showAlertInfo(\"××××您正在使用车票代售点进行购票,每张票将会收取5元手续费!××××\");"
  38. + "station.sellTicket();"
  39. + "this.takeHandlingFee();"
  40. + "this.showAlertInfo(\"××××欢迎您的光临,再见!××××\");}");
  41. cc.addMethod(sellTicket);
  42. //添加inquire方法
  43. CtMethod inquire = CtMethod.make("public void inquire() {}", cc);
  44. inquire.setBody("{this.showAlertInfo(\"××××欢迎光临本代售点,问询服务不会收取任何费用,本问询信息仅供参考,具体信息以车站真实数据为准!××××\");"
  45. + "station.inquire();"
  46. + "this.showAlertInfo(\"××××欢迎您的光临,再见!××××\");}"
  47. );
  48. cc.addMethod(inquire);
  49. //添加widthraw方法
  50. CtMethod withdraw = CtMethod.make("public void withdraw() {}", cc);
  51. withdraw.setBody("{this.showAlertInfo(\"××××欢迎光临本代售点,退票除了扣除票额的20%外,本代理处额外加收2元手续费!××××\");"
  52. + "station.withdraw();"
  53. + "this.takeHandlingFee();}"
  54. );
  55. cc.addMethod(withdraw);
  56. //获取动态生成的class
  57. Class c = cc.toClass();
  58. //获取构造器
  59. Constructor constructor= c.getConstructor(Station.class);
  60. //通过构造器实例化
  61. TicketService o = (TicketService)constructor.newInstance(new Station());
  62. o.inquire();
  63. cc.writeFile("D://test");
  64. }
  65. }

上述代码执行过后,会产生StationProxy的字节码,并且用生成字节码加载如内存创建对象,调用inquire()方法,会得到以下结果:
Mybatis之Javassist字节码生成开源框架 - 图4
通过上面动态生成的代码,我们发现,其实现相当地麻烦在创造的过程中,含有太多的业务代码。我们使用上述创建Proxy代理类的方式的初衷是减少系统代码的冗杂度,但是上述做法却增加了在动态创建代理类过程中的复杂度:手动地创建了太多的业务代码,并且封装性也不够,完全不具有可拓展性和通用性。如果某个代理类的一些业务逻辑非常复杂,上述的动态创建代理的方式是非常不可取的!


版权声明:本文为CSDN博主「亦山」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/luanlouis/article/details/24589193