Webx3項目是運行在jetty/tomcat這種Web應用容器中的,Web應用的模式都是請求-響應的。一個請求經過瀏覽器發出,封裝爲HTTP報文到達服務端,被容器接受到,封裝爲HttpRequest和HttpResponse等,而後進入Webx3的領域,經過Webx3的一套Pipeline機制到達指定的後臺,調用Java類獲取數據或處理,渲染Velocity模板並返回到客戶端進行顯示。簡單示意圖以下:php
pipeline:html
下面經過分析 Webx3學習筆記(1)——Hello, World!中生成的源碼來理解Webx3的基本流程:前端
以http://localhost:8081/?home 這個頁面做爲起點吧,經過觀察源碼可知這個頁面是結合了app1/templates/layout/default.vm和app1/templates/screen/index.vm兩個模板進行渲染出來的(怎麼找到這兩個頁面的我暫時也不清楚)。這個頁面就是一系列簡單demo的集合,下面挑有表明性的幾個進行分析:java
url: http://localhost:8081/simple/say_hi.doweb
先看一下pipeline.xml中比較重要的loop部分,除了做者自己的註釋,也加上了我本身的理解:json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
<
pl-valves:loop
>
<
pl-valves:choose
>
<
when
>
<!-- 執行帶模板的screen,默認有layout。 -->
<
pl-conditions:target-extension-condition
extension
=
"null"
/>
<!-- <strong>會去查找action包下面指定類的指定方法</strong> -->
<
pl-valves:performAction
/>
<!-- <strong>會去模塊的screen包下面尋找同名的Screen類</strong>-->
<
pl-valves:performTemplateScreen
/>
<!-- <strong>會去app1/templates/screen下尋找同名的vm模板,而後再找app1/templates/layout下同名的layout模板,最後把layout和screen模板拼起來進行渲染並返回,要是實在找不到layout,就只渲染screen。</strong>
-->
<
pl-valves:renderTemplate
/>
</
when
>
<
when
>
<!-- 執行不帶模板的screen,無layout。 -->
<
pl-conditions:target-extension-condition
extension
=
"do"
/>
<!-- 同上 -->
<
pl-valves:performAction
/>
<!-- <strong>基本同上,不過這個步驟是必須的,沒法跳過。並且這種Screen類是有輸出的,相似Servlet</strong> -->
<
pl-valves:performScreen
/>
</
when
>
<
when
>
<!-- 建立JSON,無模板,無layout。 -->
<
pl-conditions:target-extension-condition
extension
=
"json"
/>
<
pl-valves:performScreen
/>
<
pl-valves:renderResultAsJson
/>
</
when
>
<
otherwise
>
<!-- 將控制交還給servlet engine。 -->
<
pl-valves:exit
/>
</
otherwise
>
</
pl-valves:choose
>
<!-- 假如rundata.setRedirectTarget()被設置,則循環,不然退出循環。 -->
<
pl-valves:breakUnlessTargetRedirected
/>
</
pl-valves:loop
>
|
本例中請求的後綴是.do,因此會執行:瀏覽器
1
2
3
4
5
|
<
when
>
<
pl-conditions:target-extension-condition
extension
=
"do"
/>
<
pl-valves:performAction
/>
<
pl-valves:performScreen
/>
</
when
>
|
performAction是表單提交纔會執行的流程,因此這裏直接略過,執行到了performScreen,由於target爲simple/say_hi.do,因此該方法去尋找app1.module.screen.simple下尋找同名Java類,並且有一個命名轉換規則:say_hi->SayHi。因而咱們就找到了SayHi.java,並調用其execute()方法。tomcat
1
2
3
4
5
6
7
8
9
10
11
|
public
class
SayHi {
@Autowired
private
HttpServletResponse response;
public
void
execute()
throws
Exception {
// 設置content type,但不須要設置charset。框架會設置正確的charset。
response.setContentType(
"text/plain"
);
// 如同servlet同樣:取得輸出流。
PrintWriter out = response.getWriter();
out.println(
"Hi there, how are you doing today?"
);
}
}
|
【注意對於第7個例子,say_hello_1.do中沒有找到execute()方法,就默認去找了doPerform(),say_hello_1/chinese.do,則會去調用其doChinese()方法。這是種不太經常使用的方式。】app
url:http://localhost:8081/form/register.htm框架
url後綴名爲.htm,會被轉換爲無後綴的形式,進行以下處理流程:
1
2
3
|
<
pl-valves:performAction
/>
<
pl-valves:performTemplateScreen
/>
<
pl-valves:renderTemplate
/>
|
這也不是一個表單請求,因此略過performAction,執行performTemplateScreen ,去app1.module.screen.form包下尋找Register.Java,並無找到,可是也不要緊【performTemplateScreen 狀況下,Screen的Java類並非必須的】,繼續執行renderTemplate,去app1/templates/screen/form/下尋找register.vm,此次找到了,再結合app1/templates/layout/default.vm進行渲染,default.vm就是一個html骨架,主要使用register.vm進行填充。
register.vm:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$page
.setTitle(
"Register"
)
<form action=
"$app1Link.setTarget("
form/register
")"
method=
"post"
>
$csrfToken
.hiddenField
<input type=
"hidden"
name=
"action"
value=
"register_action"
/>
#set (
$group
=
$form
.register.defaultInstance)
<p>Hello, what's your name?</p>
#
if
(!
$group
.name.valid)
<p>
$group
.name.message</p>
#
end
<p>
<input type=
"text"
name=
"$group.name.key"
value=
"$!group.name.value"
/>
<input type=
"submit"
name=
"event_submit_do_register"
/>
</p>
</form>
|
其中有不少細節值得注意,讓咱們來關注一下register.vm:
表單的主體部分:
1
2
3
4
5
6
7
8
|
</pre>
<pre>#set (
$group
=
$form
.register.defaultInstance)</pre>
<pre><p>Hello, what's your name?</p></pre>
<pre>#
if
(!
$group
.name.valid)
<p>
$group
.name.message</p>
#
end
<input type=
"text"
name=
"$group.name.key"
value=
"$!group.name.value"
/></pre>
<pre>
|
$form.register是在WEB-INF/app1/form.xml裏定義的:
1
2
3
4
5
6
7
8
9
|
</pre>
<pre><group name=
"register"
extends
=
"csrfCheck"
>
<field name=
"name"
displayName=
"你的名字"
>
<fm-validators:required-validator>
<message>必須填寫 ${displayName}</message>
</fm-validators:required-validator>
</field>
</group></pre>
<pre>
|
這個名爲register的FormGroup有一個屬性名爲name,該屬性還有一個erquired-validator,意思就是必填項。因此在表單內容填寫完畢後就會把值付給register的對應屬性傳到action那裏進行處理。
而後看action部分,若是咱們把<from action=」…」>中的action屬性給刪除掉,按理說提交表單時就會報錯,但是並無發生這樣的狀況,原來Webx3中的表單action是在這裏進行配置的:
1
2
3
4
5
6
|
</
pre
>
<
pre
>$csrfToken.hiddenField
<
input
type
=
"hidden"
name
=
"action"
value="<strong>register_action</
strong
>"/>
……
<
input
type
=
"submit"
name="event_submit_<strong>do_register</
strong
>"/></
pre
>
<
pre
>
|
加粗部分共同決定了這個表單提交時會去找app1.module.action包下面的RegisterAction.doregister()方法。讓咱們去看看這個方法:
1
2
3
4
5
6
|
</pre>
<pre>
public
void
doRegister(
@FormGroup
(
"register"
) Visitor visitor, Navigator nav) {
String name = visitor.getName();</pre>
<pre>nav.redirectTo(
"app1Link"
).withTarget(
"form/welcome"
).withParameter(
"name"
, name);</pre>
<pre>}</pre>
<pre>
|
@FormGroup就是剛剛說過的,這裏會讀取其屬性並注入到Visitor中。Navigator是個內置的類,直接用就好了。
這裏在接收到表單請求後會把應用重定向到form/welcome去,同時帶一個參數name,就是剛剛表單中讀取的值。
而後form/welcome會走一樣的流程:
1
2
3
4
|
<
pl-valves:performAction
/>
<
pl-valves:performTemplateScreen
/>
<
pl-valves:renderTemplate
/></
pre
>
<
pre
>
|
action略過,performTemplateScreen會找到app1.module.screen.form.Welcome.java:
1
2
3
4
5
|
</pre>
<pre>public void execute(@Param("name") String name, Context context) {
context.put("name", name);
}</pre>
<pre>
|
@Param(「name」) String name 等價於String name = HttpRequest.getParameter(「name」); 這樣寫能省幾行代碼。
這個類的做用僅僅是把request裏的name參數寫進Velocity的context裏面。
而後renderTemplate會找到app1/template/screen/form/welcome.vm:
1
2
3
4
|
</pre>
<pre>
$page
.setTitle(
"Welcome, $name"
)
<p>Welcome,
$name
!</p></pre>
<pre>
|
一樣是結合layout渲染輸出。
最終展現的界面爲:
至此一個表單的歷程已經所有走完。