Thursday, December 13, 2012

Apache Commons --- BSF

BSF 亦即 bean scripting framework 也就是对 bean 的 scripting language 方面的支持,这个支持主要是通过 BSFManager 来做到的,实现一个语言到这个 framework 里面需要继承 BSFManagerImpl 并 override 掉其中一部分方法,最核心的是 eval。有了每个语言对应的 BSFManager 我们就能用 JVM 来解析各种 scripting language 了。在 scripting language 的 domain 里面存在一个 bsf 对象,通过它可以调用 BSFManager 的方法,比如
  • registerBean
  • lookupBean
拿到了对应的 bean 之后,我们就可以在 scripting language 里面操纵这个 Java 对象。感觉这个跟 PIG 支持 scripting language 的 UDF 非常类似。BSF 可能是比较早的 framework,现在似乎都是使用 javax.scripting 的接口了,PIG 那个似乎是后者。

有一些参考的例子,比如 JDEE 里面带了一个所谓的 beanshell,这个就是 Java 语言自己的 scripting engine。

当然 BSF 其实自己就支持了不少流行的语言,如 jython、groovy、Javascript 之类的,其他的像 JRuby 自带了 BSF 的支持。

另外 BSF 自己有一个已经实现好的命令行工具供我们把玩:
java -cp bsf-2.4.0/lib/bsf.jar org.apache.bsf.Main -in test.py


BSF 现在有 3.x 版本了,似乎也开始使用 javax.scripting 的接口了。

Tuesday, December 11, 2012

Apache Commons --- Betwixt

说起 beans 一直都比较陌生,为了好好了解一下 Java 自己的哲学,于是趁研究 Betwixt 的时候学习了一下 Java beans 到底是什么东西,简单的说 beans 是符合以下要求的 Java class,
  • default constructible,即可以默认构造,但这个并不保证对应的 bean 处于 valid 状态
  • 所有的“属性”都含有 getter/setter 方法,这两个方法必须满足前者返回对应类型不带参数,后者带一个参数返回 void
  • 实现了 java.io.Serializable,这包括 readObject、writeObject 和 readObjectNoData 三个方法
复杂的说... 那可以参看这个 100+ 页的文档。很多 Java 的库会要求操作的对象满足一些 Java beans 的要求,前面了解的 beanutils 就会要求满足第二点。bean 的这些要求看起来非常的“机械”,这主要是为了实现某些 framework,比如 IDE 等,不过这种要求如果不做任何简化强加在 Java 程序员身上,那就是一种悲哀了,所以不少 IDE 还是支持自动生成这些 boilerplates 的。另外需要说明的是 beans 如果需要在 GUI 开发中使用,往往还会支持 event,这是类似 signal/slot 或者说 observer pattern。

正因为以上原因,beans 其实与某个 java package 是独立的,但是处理 beans 需要的一些手段被抽象出来,SDK 里面提供了 java.beans 这个 package,其中很多接口就能看出来主要是为了让这些 beans 与 IDE 等 framework 更好工作,比如
  • BeanInfo 获得 bean 的一些 property 等信息,Customizer 用来为 bean 某些信息进行更新
  • DesignMode 用来判断是否在设计界面(如 applet 的设计),PropertyEditor 用来帮助编辑属性

当然 java.beans 提供的一些支持还比较接近底层,特别是和 IDE 打交道的部分,同时里面还提供了两个类跟这里 Betwixt 有关,那就是 XMLDecoder 和 XMLEncoder,两者负责将一个 bean 写到 XML 文件里面或者从里面构造出一个 bean。Betwixt 的目标就是将这个转换过程变成可以控制的,通过尽量少的代价干涉定制者得到需要的效果。直接用 XMLEncoder 的话大致如下
import java.beans.XMLEncoder ;

public class TestBeanCodec {
    private int age ;

    public int getAge () {
        return age ;
    }

    public void setAge (int a) {
        age = a ;
    }

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void main (String[] args) {
        TestBeanCodec bean = new TestBeanCodec () ;
        bean.setAge (5) ;
        bean.setName ("foo") ;

        XMLEncoder encoder = new XMLEncoder (System.out) ;
        encoder.writeObject (bean) ;
        encoder.close () ;
    }
}
使用 betwixt 的话我们为每个 class 提供一个 class.betwixt 文件打包到 jar 里面,之后使用 org.apache.commons.betwixt 里面的 BeanWriter/BeanReader 就能进行定制化的输入输出了。

参看 userguideAPI例子

Sunday, December 9, 2012

apache commons --- BeanUtils

什么是 beans,一般是指有 default constructor,且有 getter/setter 的类,通常的 getter/setter 有三种:
  • 简单的就是 get 不带参数,set 传递一个,表示设置一个简单的属性
  • indexed 相当于这个属性是个 List 之类的东西,因此 get 传递 int,set 传递 int 和值
  • mapped 相当于这个属性是个 String 到某个类型的 Map,因此和 indexed 类似,但是多传递的是个 String
这个包提供了对这三类方法的封装,都使用 PropertyUtils 里面的一些静态方法,通过传递字符串来表示对应的 property,如
  • get/setSimpleProperty 需要传递对象和属性名以及值(对 setter)
  • get/setIndexedProperty 的属性名就变成一个名字后面加上类似“[1]” 表示位置的信息,当然也可以按照 Simple 的形式,里面加上 int 参数作为位置
  • get/setMappedProperty 类似加上的后缀形式是“(key)”,当然也可以多传递一个 String 表示 key
当然 PropertyUtils 还支持嵌套,即如果某个属性仍然是一个 bean,我们可以使用 getNestedProperty 来处理,这时的 property 之间用点连接。为了简化使用,PropertyUtils 提供了 get/setProperty 方法,可以自动的判断使用上面哪种方法访问到对应的 property。

由于 bean 对应的对象的属性本质上是静态(即在 load 时确定后无法进行修改,相关讨论见上篇 blog),这个 package 还提供了模拟出来的动态的 bean,实现为 DynaBean,这种对象可以用 PropertyUtils 一样的处理,这样就形成了一种形式上的抽象。DynaBean 如名所说 property 的个数是可以运行时决定的。通常我们需要使用 DynaProperty(使用一个 String 表示名字,Class 表示类型)来表述一个属性,然后使用一个 DynaProperty 数组表示一个 DynaClass 包含的所有属性,之后就能创建一个 BasicDynaClass 对象了,通过其 newInstance 方法就能创建我们的 DynaBean 对象,这些对象本身可以通过 get/set 设置前面定义的属性。

岔开说一句的是通过 ResultSetDynaClass(通过一个 ResultSet 创建的 DynaClass 对象)我们可以很容易将使用 JDBC 系列获得的行转换成为 DynaClass 的对象,其 iterator 方法返回的就是一个 DynaBean 的迭代器,供我们遍历数据使用。类似另一个类称为 RowSetDynaClass 是进行的 copy,区别在于前者由于没有 copy 需要保留这个 ResultSet 对象,后者创建后就不再与 ResultSet 有关系了。

既然 DynaBean 看起来比一般的 bean 要强大,那么自然我们也希望能将一般的 bean 转换成为 DynaBean,这可以使用 WrapDynaBean 来创建。另外,通过 LazyDynaBean 我们可以在需要的时候添加/删除属性(add/remove 方法),而不是像前面的 SimpleDynaBean 要求全部都在构造的时候说明。

使用这种 bean 策略的常见用途在于某些情况下我们拿到的数据是一个 map,我们希望将对应 key 的一些数据通过转换放置到某些 bean 里面供 Java 程序使用,这个常见的操作(populate)由 BeanUtils 提供,它依赖于一个叫 ConvertUtils 的类完成一些具体的转换(字符串到某些类的转换,通过 register 这些类对应的 Converter 即可)。

最后说一下 BeanUtils 还提供了一些类似 C++ STL 里面的一些对 collection of beans 的工具,可以简化很多 code,相关的类有,BeanValuePropertyChangeClosure、BeanValueProperty*Predicate、BeanValuePopertyTransformer。

参看这个 user guideapi doc

java.lang.reflect

说到 Java 自己的 reflection 一直不是很熟悉,这里稍微讨论一下几个相关的类。虽然标题写的是 java.lang.reflect 但是实际在 java.lang 里面也有不少息息相关的类。

那么第一个就是 ClassLoader 了,这算是 Java 的 dynamic linker 吧,说到底 Java 仍然不是一个动态的语言,比如 python 之流甚至可以动态(运行时)的为一个对象添加属性、方法,这在 Java 里面仍然是一个很困难的事情。JVM 获得一个 class 是通过 ClassLoader 做到的,这个东西本身的也很复杂(是一个层次结构,子类 load 之后需要在父类里面继续传递),有机会再仔细看看,但就系统自带的 Loader 拿到了这个 class 之后这个 class 相关的信息就可以用 Class 类来获取,当然 Class.forName 可以帮助我们从字符串找到需要的类的 Class 对象(这个区别就在于如果直接写 Foo.class 是编译时的,而字符串转换的话是运行时的)。

那么我们的故事大约就从 Class 开始,如何表述一个 Class 的方法和成员呢,java 提供了 Member 接口和 Method 这个类,可以 getName 获得成员名、方法名,后者还能 invoke,这样就相当于 C++ 里面通过 mem_fn 把方法拿出来做成的 functor 了。static 之类的修饰可以用 Modifier 来描述。构造函数单独用 Constructor 来表示。另外有点关系的就是 Package。这样我们可以通过 Class 对象拿到我们需要的不少信息了。

不过有时候我们希望存在一个对象,能够帮助我们 handle 任何可能的请求,那怎么办?比如我们常见 bean 他们都有 getter/setter,如果我们希望捕获任意的 Method,那是不是可以呢?在 Java 里面实现这个很难,但是如果你限制这些 Method 为某个/些 interface 里面声明的那些,我们其实还是可以通过 Proxy 来实现的,Proxy 通常用 factory method Proxy.newProxyInstance 来产生,传入的参数分别是 ClassLoader,一个 interface 的数组以及响应方法的 InvokationHandler 接口的实现。其实这个 Proxy 对象干的事情很简单,就是实现了所有的 interface,但实现的很简单,就是将对应的调用使用 Method 传递给 InvokationHandler,使用其 invoke 方法,这个方法传递 Proxy 对象本身,Method 与相关的参数列表 Object[] args,这样我们就能在这个 handler 里面决定如何 dispatch 这些方法(或者自己实现对应的请求)。

这一篇文章讲了一下如何能够 runtime 更新一个接口的实现,这类似 django/ruby on rails 那种的对开发还是很有帮助的。这里的窍门就是怎么样在一个类更新后产生新的 instance 替换掉原先的,那么借助 Proxy 就能将对应的调用传递给更新后的对象了。

这篇文章讲了一些 Proxy 在实际问题里面的三种用法
  • spring 里面实现了某种 interface 的 proxy,可以在将调用forward 之前设置好数据库连接一些相关的属性
  • testing 里面测试某个 interface 时通过一个 proxy 将其行为 wrap 起来,这样就可以在调用前后干点别的什么
  • AOP 里面实现在某个方法之前之后加入的 aspect