1. Tapestry Specifications
所有的Specification,不管扩展名是什么,内容都是XML文件。
1.1. Application Specification - ${servlet.name}.application
每个应用程序一般只有一个。
1.2. Library Specification - ${library.name}.library
和Application Specification的结构基本相当,一般每个组件库的jar中一个。
1.3. Page Specification - ${page.name}.page
一个页面一个,应用程序是由许多Page组成。
1.4. Component Specification - ${component.name}.jwc
每个组件一个,如文本框,日期选择等。每个Page可以包含许多Component,每个Component中还可以包含许多Component。
2. 让Tapestry找到你的Application Specification
关联源文件: org.apache.tapestry.services.impl.ApplicationSpecificationInitializer
Application Specification,顾名思义,应用程序定义书。一般情况下,一个WEB应用程序只有一个。文件名为${servlet.name}.application。 其中${servlet.name}为web.xml中配置ApplicationServlet的Servlet名称,其放置的路径必须是/WEB-INF/${servlet.name}.application 或者/WEB-INF/${servlet.name}/${servlet.name}.application如:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
" http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Components for Tapestry Examples Web Application</display-name>
...
<servlet>
<servlet-name>comp4t-examples</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>.
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>comp4t-examples</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
...
</web-app>
这样配置的web.xml文件的话,那么Application Specification应该为comp4t-examples.application, 其位于的路径为/WEB-INF/comp4t-examples.application或者/WEB-INF/comp4t-examples/comp4t-examples.application
3. 让Tapestry找到你的Page Specification
关联源文件: org.apache.tapestry.resolver.PageSpecificationResolverImpl
3.1. 置Page Specification于Application Specification相同路径中
最简单,最直接的办法就是这个,只要和Application Specification保持相同路径,就不怕Tapestry找不到它。
3.2. 置Page Specification于/WEB-INF/中
3.3. 置Page Specification于/WEB-INF/${servlet.name}/中
3.4. 置Page Specification于/中,即WEB应用程序最根目录中
3.5. 置Page Specification于相应java文件路径下,但需Application/Library Specification中配置其相应路径。
一般可以放置相应的java文件相同的路径下,如com.javaforge.comp4t.examples.FrameworkComponentExamples, 那么把Page Specification放在/com/javaforge/comp4t/examples/下比较整齐。然后须在Application Specification指定。
<application name="Components for Tapestry Examples">
...
<page name="FrameworkComponentExamples" specification-path="/com/javaforge/comp4t/examples/FrameworkComponentExamples.page"/>
<page name="Home" specification-path="/com/javaforge/comp4t/examples/Home.page"/>
...
</application>
这样的好处是所有的Tapestry Page都列在了Application Specification。便于管理和整理。
4. 管理服务器端状态
每次提交中需要保存以备后续请求处理中使用的数据。
4.1. 保持住Page的属性(Page属性的持久化)
提到Session,大家不会陌生,因为我们要想在其他的服务请求中得到之前的状态,就用到它,最典型的就是用户验证后的登陆信息。最普通的做法就是在用户正常的Login之后,把用户的信息放到Session中,在后面处理中需要用户信息时,从Session中取出。在Tapestry中是不推荐直接使用Session的。框架的作用是什么?就是用封装,抽象还使你需要做的工作得到简化。
下面是一个简单的例子,在Tapestry中我们不再需要和Session打交道。Tapestry已经为你做好了这一切。
public abstract class FrameworkComponentExamples extends BasePage {
...
public abstract List getRecords();
public abstract void setRecords(List records);
...
}
<page-specification class="com.javaforge.comp4t.examples.FrameworkComponentExamples">
...
<property name="records" persist="session"/>
...
</page-specification>
这样的配置非常明确的告诉了Tapestry将FrameworkComponentExamples页面的records属性放置到Session中。在以后的调用中, 只要你调用了getRecords()方法,那么Tapestry会从Session中取出这个对象,要是你调用了setRecords(myList),那么Tapestry会将myList设置到Session中,那么Session中的键值是如何维护的呢,我们并没有提供给Tapestry呀,一句话,Tapestry自己拼凑出来的。 后面提到拼凑方法。
需要注意的一点,我们的Page中的属性必须是abstract的, Tapestry文档中会说到,不用abstract也可以,但我告诉你,最好使用上面的写法。想象一下,只有是抽象的,不提供实现,才有可能是从Session或Client中取得属性值, 如果你给他写了实现,而你的实现很可能定义一个成员变量,那到底是从你这个成员变量中取,还是从Session中取。
4.2. 关于Page属性的Session Key
关联源文件: org.apache.tapestry.record.SessionPropertyPersistenceStrategy
拼凑的方法是${applicationId},${pageName},${idPath},${propertyName}其中${idPath}可能没有,
${applicationId}为前面提到的Servlet名,即web.xml中配置的ApplicationServlet的Servlet名,如comp4t-examples。
${pageName}为页面名,如FrameworkComponentExamples。
如果该页面中调用和其他组件,而且组件中的某个属性指定了持久化(persist)属性,那么${idPath}就会存在。
${propertyName}为属性名,如records。
这样的话整个Session Key为comp4t-examples,FrameworkComponentExamples,records,不会重复。
4.3. 页面属性的持久化策略(PersistenceStrategy)
tapestry.persist.xml
<contribution configuration-id="PersistenceStrategy">
<strategy name="session" object="service:SessionPropertyPersistenceStrategy"/>
<strategy name="client" object="service:PageClientPropertyPersistenceStrategy"/>
<strategy name="client age" object="service:PageClientPropertyPersistenceStrategy"/>
<strategy name="client:app" object="service:AppClientPropertyPersistenceStrategy"/>
</contribution>
5. 构造友好的URL。
何为不友好的URL,如
/comp4t-examples/app?component=%24DirectLink&page=Home&service=direct&sp=S123456
/comp4t-examples/app?digest=a4008189d74c98218e216c1e2ce53193&path=%2Fcom%2Fjavaforge%2Fcomp4t%2Fexamples%2Fstyle.css&service=asset
上面的的两例称之为非友好URL,为什么呢,太乱了,不仔细分析是无法弄清其中道理,那为什么会存在呢?因为机器能看懂。虽然乱,但也是标准的URL。
看看下面两例
/comp4t-examples/Home,$DirectLink.d?sp=S123456
/comp4t-examples/assets/a4008189d74c98218e216c1e2ce53193/com/javaforge/comp4t/examples/style.css
虽然没有达到最佳效果,但是进步是很明显的,他和上面的可以表达相同的信息量,因为都能被Tapestry解释。
当我们不对Tapestry进行任何配置的时候,在程序运行过程中,出现的URL是第一种,也是在Tapestry 3.x中固定使用的一种。 但在Tapestry 4.0中有了很大的改观,只要稍加配置,即可形成第二种的效果。
或许你对Hivemind还不了解, 但是我不得不告诉你,Tapestry 从4.0 开始基于Hivemind,可以这样说,不了解Hivemind, 你便精通不了Tapestry。 虽然你很可能不想涉入新的东西,但是新的东西也会给你带来新的效果。你的学习不会浪费。
下面就是一段Hivemind配置,来达到第二种URL效果。位于classpath:/META-INF/hivemodule.xml文件中
<module id="comp4t.examples" version="1.0.0" package="com.javaforge.comp4t.examples">
...
<contribution configuration-id="tapestry.url.ServiceEncoders">
<page-service-encoder id="page" extension="html" service="page"/>
<direct-service-encoder id="direct" stateless-extension="d" stateful-extension="sd"/>
<asset-encoder id="asset" path="/assets/"/>
</contribution>
...
</module>
其实很多时候,我们并不需要知道是什么意思,只需要知道代码要写的位置,和能起到的效果。 但是如果你真想知道其中原委,那我给你解释。 TODO:解释
6. 对象和字符串之间的转换 - DataSqueezer
关联源文件: tapestry.data.xml
在WEB 应用中,不可避免出现对字符串和对象进行转换,因为浏览器表单数据或者是HTTP URL,或者Cookie全部都是字符串,而我们的应用中,非常普遍的做法,就是对提交上来的数据和对象进行绑定,在Struts和Spring中用表单中项目的名称和对象的属性进行对应(并做了简单原始类型的转换)。 但在Tapestry中做法是不同的,DataSqueezer和SqueezeAdaptor起着关键性的作用。
我们简单看一下Tapestry中的Test,就不难发现其中转换的规律。
org.apache.tapestry.junit.utils.TestDataSqueezer
public void testBoolean()
{
attempt(Boolean.TRUE, "T" ;
attempt(Boolean.FALSE, "F" ;
}
public void testNull()
{
attempt(null, "X" ;
}
public void testByte()
{
attempt(new Byte((byte) 0), "b0" ;
attempt(new Byte((byte) -5), "b-5" ;
attempt(new Byte((byte) 72), "b72" ;
}
public void testFloat()
{
attempt(new Float(0), "f0.0" ;
attempt(new Float(3.1459), "f3.1459" ;
attempt(new Float(-37.23), "f-37.23" ;
}
public void testDouble()
{
attempt(new Double(0), "d0.0" ;
attempt(new Double(3.1459), "d3.1459" ;
attempt(new Double(-37.23), "d-37.23" ;
}
public void testInteger()
{
attempt(new Integer(0), "0" ;
attempt(new Integer(205), "205" ;
attempt(new Integer(-173), "-173" ;
}
public void testLong()
{
attempt(new Long(0), "l0" ;
attempt(new Long(800400300l), "l800400300" ;
attempt(new Long(-987654321l), "l-987654321" ;
}
public void testShort()
{
attempt(new Short((short) 0), "s0" ;
attempt(new Short((short) -10), "s-10" ;
attempt(new Short((short) 57), "s57" ;
}
/** @since 2.2 * */
public void testCharacter()
{
attempt(new Character('a'), "ca" ;
attempt(new Character('Z'), "cZ" ;
}
public void testString()
{
attempt("Now is the time for all good men ...", "SNow is the time for all good men ..." ;
attempt("X marks the spot!", "SX marks the spot!" ;
attempt("So long, sucker!", "SSo long, sucker!" ;
}
public void testComponentAddress()
{
ComponentAddress objAddress = new ComponentAddress("framework irectLink",
"component.subcomponent" ;
attempt(objAddress, "Aframework irectLink,component.subcomponent" ;
objAddress = new ComponentAddress("framework irectLink", null);
attempt(objAddress, "Aframework irectLink," ;
}
每种类型都对应一个或者一串前缀,一个字符串如果开头单个字母是前缀中的一个的时候,那么就被确定为该类型,如"T"是"TF"中之一,那么"T"字符串被确定为Boolean形。
Table 1. SqueezeAdaptor
类型 Adaptor 前缀 举例
null X X
Boolean BooleanAdaptor TF T, F
Byte ByteAdaptor b b127 ,b0, b-5
Character CharacterAdaptor c ca, cZ
Double DoubleAdaptor d d0.0, d3.1459, d-37.23
Float FloatAdaptor f f0.0, f3.1459, f-37.23
Integer IntegerAdaptor -0123456789 0, 205, -173
Long LongAdaptor l l0, l800400300, l-987654321
Short ShortAdaptor s s0, s-10, s57
String StringAdaptor S SNow is the time, SX marks
Serializable SerializableAdaptor OZ O123NWEQ..., Z23423EXF...
ComponentAddress ComponentAddressAdaptor A Aframework irectLink,component.subcomponent
7. Form处理的核心阶段Rewind
所有的Form组件(Form Control Component),如:文本框,文本域,提交按钮,选择列表等等(但是Form本身不属于此列,它只是一个装Form组件的容器) 都继承自AbstractFormComponent,在AbstractFormComponent中有一个非常重要的方法rewindFormComponent。当画面中的Form进行提交时,第一个阶段就是Rewind阶段。 Form对象会首先进行循环调用其中所有的Form组件的rewindFormComponent方法。
根据Form组件的性质,每个 Form组件中的rewindFormComponent方法的实现是不同的,如文本框,列表,单选,多选等都是简单的把提交上去的值重新付给这些组件对象,并且在同时进行了对Page中相应属性的绑定(即把这些值更新到Page的属性当中去)。但是如提交按钮,提交链接中所做的则是把Template中指定的监听加到Form对象当中去,以便Rewind过程结束之前,调用这些Page中的监听方法(FormSupportImpl的runDeferredRunnables()方法)。
8. Tapestry中的Validation
8.1. Tapestry中提供的Validator
Tapestry中提供了许多Validator,我们直接使用他们就可能完成大部分通常的Check。
关联源文件tapestry.form.validator.xml
<contribution configuration-id="Validators">
<validator name="email" configurable="false" class="Email"/>
<validator name="required" configurable="false" class="Required"/>
<validator name="max" class="Max"/>
<validator name="maxDate" class="MaxDate"/>
<validator name="maxLength" class="MaxLength"/>
<validator name="min" class="Min"/>
<validator name="minDate" class="MinDate"/>
<validator name="minLength" class="MinLength"/>
<validator name="pattern" class="Pattern"/>
</contribution>
8.2. 简单的例子
<input type="text" jwcid="minAndMax@TextField"
value="ognl:minAndMax"
translator="translator:number,pattern=#####"
displayName="literal:Min,Max"
validators="validators:min=3,max=5"/>
上面是一个非常简单的例子,达到的目的就是改组件对应的Page的int属性minAndMax必须在3和5之间,由于Page的minAndMax属性为 int型, 不是字符串,所以我们必须告诉Tapestry如何将页面中的值转换为Page的属性,所以这里面就指定了translator。
8.3. Validation Specification - validators属性的格式
关联源文件org.apache.tapestry.form.validator.ValidatorFactory
里面的内容前缀首先是validators:,Validator之间用逗号分隔,每个Validator的格式可以是下面形式
1.
name
2.
name=value
3.
name[message]
4.
name[%message-key]
5.
name=value[message]
6.
name=value[%message-key]
7.
$name
其中name为Validator名,如:email, required, max, maxDate, min等等。
value为传入的参数值,如:min=3, max=5等。
message为当该Validator校验失败后显示的Message。
message -key为本地化文件中Message的Key值, 如Tapestry Page Name为Login的话,那么相应的有Login.html,Login.page,Login.properties,Login_zh_CN.properties等,这里面的两个properties文件就是本地化文件。
下面是具体的例子
ValidationExamples.html中
<input type="text" jwcid="minAndMaxWithMessage@TextField"
value="ognl:minAndMaxWithMessage"
translator="translator:number,pattern=#####"
displayName="literal:Min,Max"
validators="validators:min=3[%must-larger-than-3],max=5[Must less than 5]"/></td>
那么在ValidationExamples.properties中为
must-larger-than-3=Field {0} must larger than 3.
需要注意的是,其中的{0}参数会被自动用Field名替换。
$name中的name是对一个bean的引用,该bean可以在Page Specification中定义。
如在ValidationExamples.page中定义为
<bean name="zipValidator" class="org.apache.tapestry.form.validator.Pattern">
<set name="pattern" value="message:zip-code-pattern"/>
<set name="message" value="message:zip-code-message"/>
</bean>
那么在ValidationExamples.html中就可以像下面这样引用。
<input type="text" jwcid="pattern@TextField"
value="ognl attern"
displayName="literal:Pattern"
validators="validators:$zipValidator"/>
9. 特殊用语的解释
9.1. classpath:/META-INF/hivemodule.xml中的classpath
表示在classpath下/META-INF/中的hivemodule.xml文件 , 所谓classpath,在WEB应用程序中,通常指/WEB-INF/lib下的每一个jar中,或者/WEB-INF/classes下。
9.2. ${servlet.name}.application中的${}
是一个替代符号,或者叫变量名,上面的表达表示一个通用的名字,如在应用程序中可能具体为MyTapestryExamples.application, PetStore.application等。
loading...