Tapestry最大的的一個特點就是通過字節碼生成在運行時動態的創建頁面的實例。這也是整個框架能有如此活力的基礎。通過運行時的代碼生成不僅使說明(specification),模板(template)和類結合起來形成一個統一的整體,更主要的是使程序員得到了極大的解放。所有重複性的,易出錯的煩瑣工作都有框架負責,動態的將必須的信息賦給頁面。程序員需要的僅僅是關心一些最爲基本的設置。
Tapestry4對這個生成的過程作了一個較大的調整。和其它改進類似,頁面生成也是通過HiveMind來組織的。將各種不同的生成策略使用接口分離開,然後再通過HiveMind的服務和配置組織在一起。
與代碼生成有關的主要對象大致可以分爲三類。第一個是工廠方法,負責共的生成調度緩存的工作。另一類是基本生成工作類(enhance),負責各種不同的類型的代碼生成工作。最後一個是對基本生成工作類的一個擴展,主要用於生成注入屬性的字節碼。至於如何實際生成字節碼可以參見javassist。
工廠方法(ComponentConstructorFactory):整個項目只有一個實例,主要是提供一個統一的服務接口。
基本生成類(EnhancementWorker):通過HiveMind組成成了一個責任鏈的形式(四人幫的 chain 模式),生成時會逐個調用各個實現類生成具體字節碼。
注入擴展類(InjectEnhancementWorker):這個擴展由一個基本生成類調用。由key/value的形式保存組織,用戶可以根據自己的需要添加實現。key是該擴展的名稱,value是具體的實現類。
上圖沒有列出所有的實現類。EnhancementWorker接口的主要實現有十多類,分別負責資源信息(InjectMessagesWorker)、說明(InjectSpecificationWorker)、定義屬性(SpecifiedPropertyWorker)、參數(ParameterPropertyWorker)、注入分配(DispatchToInjectWorker)、組件(InjectComponentWorker)、Bean(InjectBeanWorker)、Asset(InjectAssetWorker)、抽象屬性(AbstractPropertyWorker)、頁面的接口(pageBeginRender等五類接口,InjectPageDetachListenerWorker,InjectPageAttachListenerWorker,InjectPageValidateListenerWorker,InjectPageBeginRenderListenerWorker,InjectPageEndRenderListenerWorker)等的生成工作。這十多個類的組成一個有序的 chain,抽象屬性是這個序列的分水嶺。大體順序與我上面提到的一致,在抽象屬性前的爲一個集合,每一個都必須在抽象屬性之前,但是他們之間沒有順序的限定。抽象屬性之後的是一個集合。AnnotationEnhancementWorker 比較特殊,只有在java5以上纔有效,它實際上是其它EnhancementWorker的一個調度。如果存在它會排在這個 chain 的最前面。
注入分配(DispatchToInjectWorker)有它自己另外一套的配置,主要用戶便於用戶擴展注入的屬性。框架提供的主要有六類。InjectMetaWorker,InjectObjectWorker,InjectPageWorker,InjectScriptWorker,InjectStateFlagWorker,InjectStateWorker。
以下是我編寫的一個簡單的組件,包含了其中所有的典型的生成字節碼,並註明了是由誰生成的(因爲考慮到複用,有些屬性由第一個生成該屬性的生成類完成):
/** InjectMessagesWorker */
private org.apache.tapestry.services.ComponentMessagesSource _$componentMessagesSource;
private org.apache.hivemind.Messages _$messages;
/** InjectSpecificationWorker */
private org.apache.tapestry.spec.IComponentSpecification _$specification;
/** SpecifiedPropertyWorker */
private java.lang.String _$sessionProperty;
private java.lang.String _$sessionProperty$default;
private java.lang.String _$specifiedProperty;
private java.lang.String _$specifiedProperty$default;
/** ParameterPropertyWorker */
private java.lang.String _$parameter;
private java.lang.String _$parameter$Default;
private boolean _$parameter$Cached;
private java.lang.Class _class$java$lang$String;
/** DispatchToInjectWorker & InjectMetaWorker */
private org.apache.tapestry.services.ComponentPropertySource _$componentPropertySource;
private org.apache.tapestry.coerce.ValueConverter _$valueConverter;
/** DispatchToInjectWorker & InjectObjectWorker */
private org.apache.tapestry.engine.IEngineService _$injectObject;
/** DispatchToInjectWorker & InjectScriptWorker */
private org.apache.tapestry.enhance.DeferredScript _$script;
/** DispatchToInjectWorker & InjectStateWorker */
private java.lang.Object _$injectState;
/** DispatchToInjectWorker & (InjectStateWorker or InjectStateFlagWorker) */
private org.apache.tapestry.engine.state.ApplicationStateManager _$applicationStateManager;
/** InjectComponentWorker */
private java.lang.Class _class$org$apache$tapestry$components$Insert;
private org.apache.hivemind.Location _$injectComponent$location;
private org.apache.tapestry.components.Insert _$injectComponent;
/** AbstractPropertyWorker */
private java.lang.String _$abstractProperty;
private java.lang.String _$abstractProperty$defaultValue;
/** 構造函數,信息由多個相關EnhancementWorker提供 */
public $Enhance_21(org.apache.tapestry.services.ComponentMessagesSource $1,
org.apache.tapestry.spec.IComponentSpecification $2, java.lang.Class $3,
org.apache.tapestry.services.ComponentPropertySource $4,
org.apache.tapestry.coerce.ValueConverter $5,
org.apache.tapestry.engine.IEngineService $6,
org.apache.tapestry.enhance.DeferredScript $7,
org.apache.tapestry.engine.state.ApplicationStateManager $8, java.lang.Class $9,
org.apache.hivemind.Location $10) {
// InjectMessagesWorker
_$componentMessagesSource = $1;
// InjectSpecificationWorker
_$specification = $2;
// ParameterPropertyWorker or other(公用屬性只需生成一次,由最先生成的完成)
_class$java$lang$String = $3;
// DispatchToInjectWorker & InjectMetaWorker
_$componentPropertySource = $4;
_$valueConverter = $5;
// DispatchToInjectWorker & InjectObjectWorker
_$injectObject = $6;
// DispatchToInjectWorker & InjectScriptWorker
_$script = $7;
// DispatchToInjectWorker & (InjectStateWorker or InjectStateFlagWorker)
_$applicationStateManager = $8;
// InjectComponentWorker
_class$org$apache$tapestry$components$Insert = $9;
_$injectComponent$location = $10;
}
/** ParameterPropertyWorker */
public void cleanupAfterRender(org.apache.tapestry.IRequestCycle $1) {
super.cleanupAfterRender($1);
org.apache.tapestry.IBinding parameterBinding = getBinding("parameter");
if (_$parameter$Cached && !parameterBinding.isInvariant()) {
_$parameter$Cached = false;
_$parameter = _$parameter$Default;
}
}
/** InjectComponentWorker */
public org.apache.tapestry.components.Insert getInjectComponent() {
return _$injectComponent;
}
/** InjectSpecificationWorker */
public void setSpecifiedProperty(java.lang.String $1) {
_$specifiedProperty = $1;
}
/** DispatchToInjectWorker & InjectStateFlagWorker */
public boolean isInjectStateFlag() {
return _$applicationStateManager.exists("visit");
}
/** DispatchToInjectWorker & InjectPageWorker */
public org.apache.tapestry.IPage getInjectPage() {
return getPage().getRequestCycle().getPage("Home");
}
/** InjectAssetWorker */
public org.apache.tapestry.IAsset getAsset() {
return getAsset("$template");
}
/** InjectSpecificationWorker */
public org.apache.tapestry.spec.IComponentSpecification getSpecification() {
return _$specification;
}
/** DispatchToInjectWorker & InjectMetaWorker */
public java.lang.String getInjectMeta() {
java.lang.String meta = _$componentPropertySource.getComponentProperty(this, "meta");
return (java.lang.String) _$valueConverter.coerceValue(meta, _class$java$lang$String);
}
/** DispatchToInjectWorker & InjectObjectWorker */
public org.apache.tapestry.engine.IEngineService getInjectObject() {
return _$injectObject;
}
/** InjectMessagesWorker */
public org.apache.hivemind.Messages getMessages() {
if (_$messages == null)
_$messages = _$componentMessagesSource.getMessages(this);
return _$messages;
}
/** AbstractPropertyWorker */
public void setAbstractProperty(java.lang.String $1) {
_$abstractProperty = $1;
}
/** InjectBeanWorker */
public pojo.Bean getBean() {
return (pojo.Bean) getBeans().getBean("bean");
}
/** SpecifiedPropertyWorker */
public java.lang.String getSpecifiedProperty() {
return _$specifiedProperty;
}
/** ParameterPropertyWorker */
public java.lang.String getParameter() {
if (_$parameter$Cached)
return _$parameter;
org.apache.tapestry.IBinding binding = getBinding("parameter");
if (binding == null)
return _$parameter$Default;
java.lang.String result = (java.lang.String) binding.getObject(_class$java$lang$String);
if (isRendering() || binding.isInvariant()) {
_$parameter = result;
_$parameter$Cached = true;
}
return result;
}
/** DispatchToInjectWorker & InjectStateWorker */
public java.lang.Object getInjectState() {
if (_$injectState == null)
_$injectState = (java.lang.Object) _$applicationStateManager.get("visit");
return _$injectState;
}
/** AbstractPropertyWorker */
public java.lang.String getAbstractProperty() {
return _$abstractProperty;
}
/** 頁面讀入初始化函數,信息由多個相關EnhancementWorker提供 */
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);
// SpecifiedPropertyWorker
_$sessionProperty$default = _$sessionProperty;
_$specifiedProperty$default = _$specifiedProperty;
// InjectComponentWorker
_$injectComponent = (org.apache.tapestry.components.Insert) org.apache.tapestry.TapestryUtils
.getComponent(this, "injectComponent",
_class$org$apache$tapestry$components$Insert, _$injectComponent$location);
_$abstractProperty$defaultValue = _$abstractProperty;
// InjectPageDetachListenerWorker
getPage().addPageDetachListener(this);
// InjectPageAttachListenerWorker
getPage().addPageAttachListener(this);
// InjectPageValidateListenerWorker
getPage().addPageValidateListener(this);
// InjectPageBeginRenderListenerWorker
getPage().addPageBeginRenderListener(this);
// InjectPageEndRenderListenerWorker
getPage().addPageEndRenderListener(this);
}
/** DispatchToInjectWorker & InjectStateWorker */
public void setInjectState(java.lang.Object $1) {
_$applicationStateManager.store("visit", $1);
_$injectState = $1;
}
/** SpecifiedPropertyWorker */
public java.lang.String getSessionProperty() {
return _$sessionProperty;
}
public void setSessionProperty(java.lang.String $1) {
org.apache.tapestry.Tapestry.fireObservedChange(this, "sessionProperty", $1);
_$sessionProperty = $1;
}
/** ParameterPropertyWorker */
public void setParameter(java.lang.String $1) {
if (!isInActiveState()) {
_$parameter$Default = $1;
return;
}
org.apache.tapestry.IBinding binding = getBinding("parameter");
if (binding == null)
throw new org.apache.hivemind.ApplicationRuntimeException(
"Parameter 'parameter' is not bound and can not be updated.");
binding.setObject($1);
if (isRendering()) {
_$parameter = $1;
_$parameter$Cached = true;
}
}
/** 清空使用過的屬性,信息由多個相關EnhancementWorker提供 */
public void pageDetached(org.apache.tapestry.event.PageEvent $1) {
super.pageDetached($1);
// SpecifiedPropertyWorker
_$sessionProperty = _$sessionProperty$default;
_$specifiedProperty = _$specifiedProperty$default;
// DispatchToInjectWorker & InjectStateWorker
_$injectState = null;
// AbstractPropertyWorker
_$abstractProperty = _$abstractProperty$defaultValue;
}
/** DispatchToInjectWorker & InjectScriptWorker */
public org.apache.tapestry.IScript getInjectScript() {
return _$script.getScript();
}
}
下面是這個組件的java類:
import org.apache.tapestry.BaseComponent;
import org.apache.tapestry.IAsset;
import org.apache.tapestry.IPage;
import org.apache.tapestry.IScript;
import org.apache.tapestry.components.Insert;
import org.apache.tapestry.engine.IEngineService;
import org.apache.tapestry.engine.PageService;
import org.apache.tapestry.event.PageAttachListener;
import org.apache.tapestry.event.PageBeginRenderListener;
import org.apache.tapestry.event.PageDetachListener;
import org.apache.tapestry.event.PageEndRenderListener;
import org.apache.tapestry.event.PageEvent;
import org.apache.tapestry.event.PageValidateListener;
import pojo.Bean;
public abstract class Enhance extends BaseComponent implements PageBeginRenderListener,
PageEndRenderListener, PageAttachListener, PageDetachListener, PageValidateListener {
public abstract String getAbstractProperty();
public abstract String getSpecifiedProperty();
public abstract String getSessionProperty();
public abstract String getParameter();
public abstract Insert getInjectComponent();
public abstract Bean getBean();
public abstract IAsset getAsset();
public abstract IPage getInjectPage();
public abstract String getInjectMeta();
public abstract IEngineService getInjectObject();
public abstract IScript getInjectScript();
public abstract Object getInjectState();
public abstract boolean isInjectStateFlag();
public void pageAttached(PageEvent event) {
}
public void pageBeginRender(PageEvent event) {
}
public void pageValidate(PageEvent event) {
}
public void pageDetached(PageEvent event) {
}
}
下面是該組件的說明文件:
<!DOCTYPE component-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 4.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">
<component-specification class="com.kft.tapestry.components.Enhance">
<bean name="bean" class="pojo.Bean" property="bean"/>
<property name="specifiedProperty"/>
<property name="sessionProperty" persist="session"/>
<parameter name="parameter"/>
<component id="injectComponent" type="Insert" property="injectComponent">
<binding name="value" value="literal:mindhawk"/>
</component>
<asset name="$template" path="context:WEB-INF/component/Enhance.html" property="asset"/>
<inject property="injectPage" type="page" object="Home"/>
<meta key="meta" value="meta"/>
<inject property="injectMeta" type="meta" object="meta"/>
<inject property="injectObject" object="engine-service:page"/>
<inject property="injectScript" type="script" object="Enhance.script"/>
<inject property="injectState" type="state" object="visit"/>
<inject property="injectStateFlag" type="state-flag" object="visit"/>
</component-specification>
模版文件很簡單,不會對生成的代碼有影響,這裏就不列出來了。