Java企业教程系列

Java 7/JDK 7的Invokedynamic

  Invokedynamic字节码通过提供灵活的方法调用机制,使得JVM更加类似动态语言duck typing,因为Java是一种静态类型的语言,当你调用一个方法调用时,编译器和JVM要确保该方法调用有一个有效的方法名称、确定的参数类型,并返回某种类型(如有) 。这意味着所有的变量,方法参数和方法返回值的类型必须在运行之前是已知的。也意味着所有的变量类型必须显式声明,类型无处不在。变量不能是无类型的,并且方法不能接受无类型的参数,也不返回一个非类型化值。类型是无孔不入。

  invokedynamic字节码则改变了这种方式,JVM允许其在运行时再进行方法的这种绑定检查,这样,你能够拦截一个根本不存在的方法调用,然后将控制流程转移到另外一个方法里,做些其他事情(包括AOP的MIXIN),这也使得动态语言能够在JVM中运行,比如基于JVM的Vertx支持JS等各种语言。

  了解invokedynamic之前先了解java 7的一个新类MethodHandle.

MethodHandle 

   MethodHandle 允许对一个方法产生引用,如同对象引用一样,这样避免了繁重的反射。有了这个类,我们就可以获得方法(返回类型和参数)的信息,并使用invoke()方法调用该方法。使用MethodHandles工厂创建MethodHandle。下面代码通过MethodHandle寻找JComboBox的一个setModel方法。

 

JComboBox combo = new JComboBox();

MethodHandle handle = MethodHandles.lookup().findVirtual(JComboBox.class, "setModel",             MethodType.make(void.class, ComboBoxModel.class));

handle.invoke(combo, new CustomComboModel());

和反射相比好处是:

  • 调用 invoke() 已经被JVM优化,类似直接调用一样。
  • 性能好得多,类似标准的方法调用。
  • 当我们创建MethodHandle 对象时,实现方法检测,而不是调用invoke() 时。

InvokeDynamic

   第二心累就是invokedynamic的类。它比第一个MethodHandle更奇怪,因为一个类没有任何方法,但是,我们还是可以调用它的任何方法:

InvokeDynamic.setModel(new CustomComboModel());
InvokeDynamic.init();
Date today = InvokeDynamic.getDate();
//... You can call any valid method

  一般Java类都在编译时检查类型,而invokedynamic的调用不是在编译阶段检查,而是在运行时检查。要做到这点,我们需要定义一个bootstrap,这样在不存在的方法和我们实际方法搭起桥梁。

   当我们通过InvokeDynamic调用不存在的方法getDate()时, JVM会使用bootstrap方法来获得真正方法的MethodHandle,然后调用它。JVM缓存了MethodHandle 能提高性能,第一次动态调用将在bootstrap方法中搜索。当然,首先得注册告诉JVM我们的bootstrap方法, 这时得用Linkage.registerBootstrapMethod(String methodName) 方法.

  下面是是一个boot strap方法代码,这个方法使用一个被调用方法的名称,然后在Utility 中还是返回MethodHandle给同样方法,是没有返回参数的:

private static CallSite bootstrap(Class caller, String name, MethodType type) {
  CallSite site = new CallSite(caller, name, MethodType.make(void.class));
  site.setTarget(MethodHandles.lookup().findStatic(Utility.class, name,          MethodType.make(void.class)));
  return site;
}

   InvokeDynamic可以是任何类型,如下:

InvokeDynamic dynamicString = "Hello World";
InvokeDynamic dynamicCombo = new JComboBox();
InvokeDynamic dynamicNumber = 1;

查询案例

  比如有一个领域模型Book ,里面包括有作者 标题 ISBN和发表日期等字段,我们可以基于Book实现一些其不存在的方法调用,比如调用Book.findByTitleAndAuthor(“jdon”,“banq”)。findByTitleAndAuthor这个方法我们没有写在Book方法里面。

  我们使用invokedynamic来将findByTitleAndAuthor转为SQL语句的调用,假设我们已经有一个根据标题和作者查询书籍的SQL查询方法:

public static ResultSet query(String query, Object[] params)

  下面关键是如何将这个真正执行SQL查询query和findByTitleAndAuthor挂上钩的。这是在bootstrap中实现的。

  步骤:

  1. 首先有一个真正执行SQL的方法,比如我们这里query有两个参数,第一个是一个String,SQL的编写要根据这个参数,比如如是根据标题和作者,那么SQL语句大概是:select x from y where y.title =? and y.author=?;这个SQL语句的两个问号是query的第二个数组参数里获得,最后返回的是查询结果ResultSet类型。

  2. 使用InvokeDynamic作为进行调用,比如InvokeDynamic.<ResultSet>findByTitleAndAuthor(“jdon”, “banq”)
    public static void main(String... args) throws Throwable {
        //调用下面将导致 doBootstrap()方法被调用
          ResultSet result = InvokeDynamic.<ResultSet>findByTitleAndAuthor
    ("jdon", "banq");
             ......
          String val = InvokeDynamic.<String>findByTitleAndAuthor("Lord of the Rings", "JRR Tolkein");
       }
      
    }


  3. 定义一个bootstrap 方法,bootstrap 是实现拦截与真正实现查询。对真正方法查询进行调用,将不存在的那个方法名findByTitleAndAuthor作为Query的第一个参数传入,:query(“findByTitleAndAuthor”, new Object[]{ “jdon”, “banq”});

 

  bootstrap只被调用一次。每个类至少有一个invokedynamic和一个bootstrap方法对应,bootstrap返回一个call site 对象,它提供对目标方法的get和set操作。

import java.lang.invoke.*;
import java.sql.*;
public class Query {
   static {  
      //Bootstrap 方法
    Linkage.registerBootstrapMethod("doBootstrap");
   }
 public static ResultSet query(String queryString, Object... params) {
      // 实现查询,返回ResultSet
   }
 public static CallSite doBootstrap(Class caller, String methodName, MethodType type) {
      MethodHandles.Lookup lookup = MethodHandles.lookup();
      //寻找那些类中被我们连接的方法
      //当前都在同一个Query类中
    Class me = lookup.lookupClass();
      //通过参数类型和返回类型继续寻找这个类中的query方法
      MethodHandle rawQuery = lookup.findStatic(me, "query"
            , MethodType.methodType(ResultSet.class, String.class, Object[].class));
       //将方法名作为找到的query() 方法第一个参数
      MethodHandle cookedQuery = MethodHandles.insertArguments(rawQuery, 0, methodName);
    //返回一个CallSite对象给JVM 继续调用
      return (new ConstantCallSite(MethodHandles.collectArguments(cookedQuery, type)));
   }

  

Vertx入门