在Tapestry4之前的版本,Tapestry使用了大量的动态调用(大部分是使用OGNL调用的),这样势必会造成大量运行效率的损失。好在大多数WEB程序的瓶颈是在访问数据库而不是在页面上,所以并没有对Tapestry的推广构成毁灭性的影响。但是随着Tapestry社区的发展,使用人群的增加,Howard Lewis Ship(Tapestry的作者)和一些支持Tapestry项目的开发者,意识到了这个问题。并作了大量的工作对性能进行改进。
在Tapestry引入HiveMind之后,Tapestry的活力明显的增强了,第三方支持包也越来越丰富。这种丰富不仅仅是组件的丰富,而是包括框架在内。其它人可以很容易的为Tapestry提供高效的服务。比方说在《使用tapestry-prop提高Tapestry运行效率》中就提到了使用代码生成的方式来改进OGNL绑定效率低的问题。tapestry-prop是通过对绑定规则的扩展提升系统效率的。
除了第三方支持外,Howard Lewis Ship本人对整个框架的低层实现也作了很大的改进,大大减少了动态调用的使用。对框架的改进主要有两个地方,一个是对页面和组件定义的属性的改进,一个对参数绑定的改进。
页面和组件属性的改进
在Tapestry3的时候,属性在初始化和丢弃(页面从页面池中取出或还回之前)都需要采用动态调用的方式设置默认值或用户定义的默认值。可想而只当页面使用属性越多性能损失肯定也就会越多。
在Tapestry4里,对属性的这种初始化工作有了很大的变化。首先,是代码生成的结构做了调整,采用了EnhancementWorker(多用于属性的生成)和InjectEnhancementWorker(用于各类注入的生成)两个接口定义的方法对页面进行代码生成的工作。这样通过HiveMind的定义就可以很容易的扩展代码生成的方式。其次,生成的属性不是只包含定义的属性,而是而外还会生成一个与它对应的默认值。这样在初始化数据的时候就可以直接通过默认值赋值而不用通过动态调用。不过要注意的是如果定义的属性有默认值的话情况就不同了,框架就不得不使就得使用绑定规则来生成值了,如果采用的是OGNL的规则初始化数据动态调用也就不可避免了。在后面会介绍一种不需要动态调用设置复杂初始值的方法。
下面是我写了一个简单的页面代码生成后的形式(所有代码全是生成的)。
org.apache.tapestry.event.PageDetachListener {
//下面三个属性每个页面都会生成,与页面属性的定义无关
private org.apache.tapestry.services.ComponentMessagesSource _$componentMessagesSource;
private org.apache.hivemind.Messages _$messages;
private org.apache.tapestry.spec.IComponentSpecification _$specification;
//页面中我注入了一个Spring的bean
private org.springframework.beans.factory.BeanFactory _$beanFactory;
//与_$beanFactory配合使用,主要用于出错提示
private org.apache.hivemind.Location _$location;
//页面中定义了一个value属性,代码生成后的形式
private java.lang.String _$value;
//用于保存value属性默认值的属性
private java.lang.String _$value$defaultValue;
//构造函数。所有页面必须的属性或注入的属性都在初始化时确定
public $Hawk_2_116(org.apache.tapestry.services.ComponentMessagesSource $1,
org.apache.tapestry.spec.IComponentSpecification $2,
org.springframework.beans.factory.BeanFactory $3, org.apache.hivemind.Location $4) {
_$componentMessagesSource = $1;
_$specification = $2;
_$beanFactory = $3;
_$location = $4;
}
//响应页面分离事件,用于初始化属性
public void pageDetached(org.apache.tapestry.event.PageEvent $1) {
_$value = _$value$defaultValue;
}
public org.apache.tapestry.spec.IComponentSpecification getSpecification() {
return _$specification;
}
//获取注入的bean对象
public com.kft.util.Decoder getDecoder() {
try {
return (com.kft.util.Decoder) _$beanFactory.getBean("Decoder");
} catch (Exception ex) {
throw new org.apache.hivemind.ApplicationRuntimeException(ex.getMessage(), _$location,ex);
}
}
public org.apache.hivemind.Messages getMessages() {
if (_$messages == null)
_$messages = _$componentMessagesSource.getMessages(this);
return _$messages;
}
//获取定义的value属性
public java.lang.String getValue() {
return _$value;
}
//页面刚生成时调用,只调一次
public void finishLoad(org.apache.tapestry.IRequestCycle $1,
org.apache.tapestry.engine.IPageLoader $2,
org.apache.tapestry.spec.IComponentSpecification $3) {
//调用父类方法
super.finishLoad($1, $2, $3);
//设置默认值,因为父类方法先调用,所以在父类方法中可以设置value的值
_$value$defaultValue = _$value;
getPage().addPageDetachListener(this);
getPage().addPageBeginRenderListener(this);
}
//设置value属性
public void setValue(java.lang.String $1) {
_$value = $1;
}
}
因为我没有定义含有初始值的属性,所以生成之后的代码很没有任何动态调用的痕迹。从上例中可以看出,注入的属性在初使化时就已经赋值了,不会再改动(在也为什么注入Spring的bean时需要使用BeanFactory获取的原因)。而对于属性就特别一点了。首先,在页面刚生成的时候会调用一次finishLoad()方法,用于设置各属性的初始值(该方法会首先调用父类的方法,因此可以把属性初始值放在这个函数里面定义,这样就不会存在动态调用)。之后,在页面还回池中之前会调用pageDetached()方法,将所有属性设置为初始值。通过以上两点的改进就消除了属性初始化对动态调用的依赖,性能得到大大的改进。
参数绑定的改进
在Tapestry3的时候定义参数时必须申明参数的方向。这样的设置既不便于理解,也会造成不必要的浪费。因为对于in为代表参数方向在调用之前需要一个设值的过程,这个过程会涉及到大量的动态调用,而且这些参数是不能在设值之前调用。
在Tapestry4里参数的实现成熟了很多,不再有方向的限制,不需要使用前的赋值过程等。它会直接的和绑定数据关联,而且还会对数据有缓存。这样一来,任何时候使用参数都不会出错,任何时候读取参数都不会重复调用绑定规则。即使使用变得方便,不需要理解复杂的函数方向问题,又减少了动态调用的次数。特别的如果和tapestry-prop配合使用效果会更好(tapestry-prop是一种代码生成绑定技术)。
下面是我编写的一个简单组件生成后的代码(所有代码全是生成的)。
//下面三个属性每个页面都会生成,与页面属性的定义无关
private org.apache.tapestry.services.ComponentMessagesSource _$componentMessagesSource;
private org.apache.hivemind.Messages _$messages;
private org.apache.tapestry.spec.IComponentSpecification _$specification;
//定义的一个参数
private java.lang.Object _$value;
//value参数的默认值
private java.lang.Object _$value$Default;
//value是否已经缓存
private boolean _$value$Cached;
//value的类类型,使用绑定规则时需要。此处的值为java.lang.Object
private java.lang.Class _class$java$lang$Object;
//构造函数,所有页面需要和不变的数据此时赋值
public $HawkCom_25(org.apache.tapestry.services.ComponentMessagesSource $1,
org.apache.tapestry.spec.IComponentSpecification $2, java.lang.Class $3) {
_$componentMessagesSource = $1;
_$specification = $2;
_class$java$lang$Object = $3;
}
public org.apache.hivemind.Messages getMessages() {
if (_$messages == null)
_$messages = _$componentMessagesSource.getMessages(this);
return _$messages;
}
public org.apache.tapestry.spec.IComponentSpecification getSpecification() {
return _$specification;
}
//设置value参数
public void setValue(java.lang.Object $1) {
//这个判断主是要是为了在finishLoad()方法里能设置参数的初始值
if (!isInActiveState()) {
_$value$Default = $1;
return;
}
org.apache.tapestry.IBinding binding = getBinding("value");
if (binding == null)
throw new org.apache.hivemind.ApplicationRuntimeException(
"Parameter 'value' is not bound and can not be updated.");
binding.setObject((java.lang.Object) $1);
if (isRendering()) {
_$value = $1;
_$value$Cached = true;
}
}
//获取value参数
public java.lang.Object getValue() {
if (_$value$Cached)
return _$value;
org.apache.tapestry.IBinding binding = getBinding("value");
if (binding == null)
return _$value$Default;
java.lang.Object result = (java.lang.Object) binding.getObject(_class$java$lang$Object);
if (isRendering() || binding.isInvariant()) {
_$value = result;
_$value$Cached = true;
}
return result;
}
//清除缓存的数据,修改缓存标志
public void cleanupAfterRender(org.apache.tapestry.IRequestCycle $1) {
super.cleanupAfterRender($1);
org.apache.tapestry.IBinding valueBinding = getBinding("value");
if (_$value$Cached && !valueBinding.isInvariant()) {
_$value$Cached = false;
_$value = _$value$Default;
}
}
}
从例子中可以看出,没有了动态调用,没有了参数使用前的赋值,所有的参数都直接通过绑定规则与实际数据关联,并且在页面被激活并且在render的时候组件还会缓存参数的数据。使用过后缓存将会清空,不影响下一次的使用。另外,还有一点从上面代码中体现不出来。那就是组件参数的默认值和属性一样也是可以在finishLoad函数里面初始化的。这样就使动态调用的次数减到了最小。
进一步提高效率的小技巧
1。对于大多数的帮定可以使用tapestry-prop的"prop:"帮定代替"ognl:"的帮定,并可以通过设置使整个应用默认使用"prop:"帮定(当然如果是真个应用默认使用"prop:"可能会有一定的危险)。
2。对于必须初始化的属性或参数可以考虑写在finishLoad方法里面,而不是用OGNL的表达式定义。
3。使用Form系列的组件(Form和必须在Form中使用的组件)最好给id这个参数赋值,特别是在页面信息比较多的时候(使用null值是一个比较好的方法,因为一般情况下name和id的作用是相似的)。因为目前所有的Form系列组件大多有这个参数,并且使用的是用OGNL初始化的方式。
通过以上改进,剩下的动态调用就很少了。如果监听函数(listener)的调用也能得到改进的话,估计就没有什么需要动态调用的了!