Spring 2.0带来的一个比较便利的好处就是更加灵活的XML配置方案。由于Annotation在设计方面的特殊地位,决定了它不会完全取代XML配置文件,所以我认为很长一段时间内,Java的server端框架不会把宝都押在Annotation上。Spring 2.0也是这样,解析XML配置文件这一工作不仅没有随着Annotation的加入而停滞,反而添加了更加灵活的配置方案–自定义namespace。

当然,自定义namespace远非说起来那么简单,又得有合适的XML Schema,还要自己实现Spring中的比较核心的接口和抽象类。下午才和Leon抱怨过,因为我无法像使用python的tuple和字典类型一样方便的访问java.util.List和java.util.Map的对象里面的数据,比如aList[12]或者aMap[‘name’],这些东西虽然通过其他方法也能解决,但毕竟不太舒服。这个问题的起因比较简单:我把一些JDBC相关的配置写在了一个db.properties文件里面,用Spring 1.2.x的时候,我习惯的做法是用一个org.springframework.beans.factory.config.PropertyPlaceholderConfigurer来把几个.properties文件解析,然后使用${name}的方法来访问某个属性所对应的值,当然这还是不够方便,我这破记性让我经常取了一些重复的属性名,非debug而不能得也。今天翻Spring 2.0 M4得文档,发现了<util:properties/>这么个好东西,可以从location指定的资源读取符合属性文件格式的配置信息,以一个PropertiesFactoryBean使框架在引用这个bean的时候可以访问到解析了的Properties对象。这个让我比较高兴,因为可以不用像以前那样把一些没什么关系的.properties文件用同一个PropertyPlaceholderConfigurer装载了,.properties文件的关系得以区分,我也不怕再取一些重复的名字了。问题又来了,就是那个比较无聊的问题:如果我想访问一个.properties文件中某个键名对应的属性值该怎么办?再联想开来,很多动态语言都提供的用键名做下标引用键值的方法,以及类似tuple这样的类型,Spring好像并没有为这种引用方法提供方便,放在Spring 1.x的时候,恐怕也得自己实现一些FactoryBean,然后在配置文件中设置一些property才能完成,很冗长。

所以我就想能不能通过自己解析一个namespace提供的配置,完成这个事情,最终效果就像这样:

<util:properties id=“dbConf” location=“/WEB-INF/db.properties”/>
<my:map-accessor id=“jdbc.url” eval=“dbConf[‘jdbcUrl’]”/>

这对我来说不太容易。虽然我对Spring 1.2.x还比较熟悉,但对Spring 2.0来说只有不到10天的经验(要是按照8小时一天计算,那也就3、4天吧…),而且Spring 2.0还在milestone,文档也不完全,我兴冲冲翻到3.2.2. Extensible XML support这章,结果只看到一个TODO,而TODO中涉及到的”Write your own namespace handler”也是没找到踪影。

不知从何入手,无聊的时候在eclipse里面从Spring 2.0 M4的代码里搜util这个namespace对应的URI:http://www.springframework.org/schema/util,结果在src/META-INF/下的spring.handlers和spring.schemas找到了线索(你也可以看到,实际上搜这个URI是搜不到的,我后来搜索的字符串是 springframework.org/schema/util),util这个namespace是由org.springframework.beans.factory.xml.UtilNamespaceHandler这个类完成的,从这个类就可以找到抽象类NamespaceHandlerSupport和接口NamespaceHandler,这样一来总算由了头绪:^)

NamespaceHandler这个接口很简单,只有两个方法,分别是通过XML元素来查找BeanDefinitionParser以及BeanDefinitionDecorator,而NamespaceHandlerSupport则提供了用于在类内部注册这两种parser的机制,使得子类可以将XML元素的本地名和一个BeanDefinitionParser以及BeanDefinitionDecorator关联起来,这样DefaultXmlBeanDefinitionParser在解析XML配置文件时遇到某个namespace时就可以通过对应的NamespaceHandler实现类最终返回BeanDefinition和BeanDefinitionHolder,完成bean的生成和注册。这个过程和Spring 1.2.x中的对应过程相差不是很多,只是一些类和方法有了改变,方便了开发者实现自己的NamespaceHandler。

具体实现自己的NamespaceHandler的时候–图省事我还是用了NamespaceHandlerSupport,涉及到的类很多,除了上面提到的BeanDefinitionParser和BeanDefinitionDecorator,还有ParserContext、BeanDefinitionRegistry、BeanDefinitionBuilder以及FactoryBean/AbstractFactoryBean,当然,也许还包括org.w3c.dom.Element :^)

具体的实现就不再赘述了,以免有浪费大家带宽的嫌疑。我在做这个NamespaceHandler时,因为Spring提供的一些FactoryBean不太适用,所以自己实现了几个FactoryBean,折腾了一会儿,如果很熟悉Spring的bean注册过程的话,这些工作根本不算什么。一般来说,如果org.springframework.beans.factory.config里面的一些FactoryBean实现不够用的话,十有八九是得自己去实现了,当然,其他一些包里面也有一些FactoryBean实现,不过一般都是和Spring的某个方面结合很紧,比如AOP和事务,很少见需要自己处理–实际上如果觉得有必要添加一些特性时,更好的方法应该是向Spring开发团队提交这部分的代码:^)

NamespaceHandler也完成了,然后就是要编写一份XML Schema了。好在我做的东西比较简单,我所具备的XML Schema手艺还能让我完成这样一份简单的schema,如果稍微困难一些只好去啃那些XML大部头了–我一向对W3C很景仰,因为那里总是诞生出十分庞大的规范,可能使用了几年之后还是会被一些自己一直没有注意到的特性吓到,当然,W3C的规范的扩展性总是很强,这点不得不Orz一下。

都完成了,最后的事情就是让Spring在遇到我的namespace时知道去调用我实现的NamespaceHandler,这个让我比较郁闷,因为默认的DefaultXmlBeanDefinitionParser是用DefaultNamespaceHandlerResolver来接管namespace和NamespaceHandler的对应关系,而不指定资源文件路径作为参数构造出的DefaultNamespaceHandlerResolver只知道从”META-INF/spring.handlers”来读取配置,我暂时还没找到自定义这个配置的方法,不过最后还是通过ClassLoader.getResources()然后通过URL.openConnection去完成读取和加载的工作,也许可以做一些trick…谁知道,太晚了,回头再说了。

多种namespace和自定义NamespaceHandler的方式无疑为Spring的XML配置文件增加了更大的威力,Spring 2.0也已经把aop、transaction/tx、jndi和Web MVC等等配置放入单独的namespace中(从spring.jar!/META-INF/spring.handlers中就能找到)。老式的bean定义虽然可以适合绝大部分的配置工作,但是在一些特殊领域难免显得冗长拖沓,这些领域往往有自己的领域定义,继续沿用老式的bean定义除了会把人搞晕以外倒也没有其他恶行,这种情景之下,使用自己的namespace然后通过自定义的NamespaceHandler去注册bean可以使得编写XML配置的时候更加符合特定领域的语义要求,增强XML文件的可读性。

很想建立一个比如Spring Namespace Handler Repository的项目,使得Spring更能适用于不同领域。

Technorati : ,