Tapestry4改進運行效率的實現方法

    在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的規則初始化數據動態調用也就不可避免了。在後面會介紹一種不需要動態調用設置複雜初始值的方法。
    下面是我寫了一個簡單的頁面代碼生成後的形式(所有代碼全是生成的)。
public class $Hawk_2_116 extends org.apache.tapestry.workbench.hawk.Hawk_2 implements
  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是一種代碼生成綁定技術)。
    下面是我編寫的一個簡單組件生成後的代碼(所有代碼全是生成的)。
public class $HawkCom_25 extends org.apache.tapestry.workbench.components.hawk.HawkCom {
     //下面三個屬性每個頁面都會生成,與頁面屬性的定義無關
 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)的調用也能得到改進的話,估計就沒有什麼需要動態調用的了! 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章