注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

和申的个人主页

专注于java开发,1985wanggang

 
 
 

日志

 
 

Webx框架之服务简介  

2011-10-18 13:00:14|  分类: WebX |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Form Service ——提供基于HTML form的表单验证功能。

Template Service ——提供基于文本的模板渲染的功能。

JSP Service ——基于JSP的模板引擎,和Template Service配合,渲染JSP格式的模板文件。

Velocity Service ——基于Velocity的模板引擎,和Template Service配合,渲染Velocity格式的模板文件。

Pipeline Service ——读取配置,创建pipelinePipeline是一种可配置的程序流程控制技术,被webx用来控制HTTP请求的流程。

Mail Service ——通过配置和编程的方法,方便地创建、发送、接收符合RFC标准的e-mail

Pull Service ——实现Pull-MVC的设计模式。

RunData Service ——HTTP请求中的诸多对象包装成一个更易于使用的接口,并且对HTTP response实现了buffer机制,是页面cache技术和页面layout技术的基础。

Upload Service ——处理multipart/form-data类型的HTTP request

URIBroker Service ——动态生成任意类型的URLURI

Webx框架之RunData

取得HTTP requestresponsesession

rundata.getRequest();

rundata.getResponse();

rundata.getSession();

取得输出流(自动buffering

rundata.getResponse().getWriter();

rundata.getResponse().getOutputStream();

取得query参数(无论是一般form还是multipart form

rundata.getParameters().getString(“id”);

rundata.getParameters().getInt(“quantity”);

内部从定向

rundata.setRedirectTarget(“homepage.vm”);

外部重定向

rundata.setRedirectLocation(“http://www.alibaba.com/”);

设置content typecharacter encoding

rundata.setContentType(“text/html”);

rundata.setCharacterEncoding(“UTF-8”);

存取request scope的参数

rundata.getAttribute(key);

rundata.setAttribute(key,object);

Webx执行的流程

当你在浏览器里敲入http://localhost:8080/workshop/hello.htm时,在Webx中发生了哪些事呢?
很简单,首先,根据web.xml中的映射,*.htm被映射到Webx Controller Servlet。因此Webx Controller Servlet会被激活来处理这个请求。而Webx Controller Servlet只做了一件事,就是:执行pipeline
所谓pipeline,即管道,它是由一个或多个“阀门Valve”构成的。我们可以想象,水从管道的一头流入,从管道另一头流出,其中经过很多个阀门。事实上,阀门可以控制水流的方向、甚至改变水分子的组成结构。这真是一个不寻常的管道。

管道是由PipelineService来创建并管理的。在上面的例子中,PipelineService读取/WEB-INF/pipeline.xml来创建管道。在请求被处理的过程中,这个管道做了哪些事呢?

1.TryCatchFinallyValve
<valve class="com.alibaba.service.pipeline.TryCatchFinallyValve">
    <try> </try>
    <catch> </catch>
    <finally> </finally>
</valve>
这个阀门类似于Java中的try-catch-finally结构。它将整个管道分成了三个分支:try分支、catch分支、finally分支。
1.    首先,try分支会被执行。
2.    在执行过程中,如果发生异常,就会转入到catch分支。通常可以在这里做错误处理的工作。
3.    无论发生异常与否,finally分支都会被执行。通常可以在这里做一些扫尾工作。
2.SetLoggingContextValve
<valve class="com.alibaba.service.pipeline.TryCatchFinallyValve">
    <try>
        <valve class="com.alibaba.turbine.pipeline.SetLoggingContextValve"/>
    </try>
    <catch></catch>
    <finally>
        <valve class="com.alibaba.turbine.pipeline.SetLoggingContextValve" action="cleanup"/>
    </finally>
</valve>
这是一个可写可不写的阀门,但是写了它以后,有助于我们在日志中发现错误的根源。
下面是我们的log4j日志文件中的一行错误信息:
2007-01-31 13:36:05,509 [/workshop/hello.htm] ERROR screen.Error - Error occurred while processing the HTTP request
发现了吗?Log4j把导致错误的请求的URL也记录在案了!这是怎么做到的呢?原来,log4j有一个叫做MDC的功能,它利用ThreadLocal变量保存当前线程的上下文信息。回看我们的log4j.xml配置:
<appender ……>
    <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="%d [%X{requestURIWithQueryString}] %-5p %c{2} - %m%n"/>
    </layout>
</appender>
其 中,%X{requestURIWithQueryString}就是引用了MDC中的内容。现在的问题是,MDC何时被设置?当然是在请求开始的时候设 置。SetLoggingContextValve做的就是这件事。最后,别忘了,当请求结束时,我们必须在finally分支中,用 SetLoggingContextValve清除MDC的内容。

MDC中可以包含哪些内容呢?事实上可以包含任何内容。SetLoggingContextValve设置了如下内容:

%X{参数名}

说明

示例

method

请求的方法

GETPOST

requestURL

完整URL,不含query string

http://localhost:8080/workshop/hello.htm

requestURLWithQueryString

完整URL,包含query string

http://localhost:8080/workshop/hello.htm?id=1

requestURI

不包含host信息的URI,不含query string

/workshop/hello.htm

requestURIWithQueryString

不包含host信息的URI,包含query string

/workshop/hello.htm?id=1

queryString

Query string

id=1&submit=true

remoteHost

用户主机名

127.0.0.1

remoteAddr

用户IP地址

127.0.0.1

userAgent

用户浏览器类型

Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)

referrer

用户是从哪个页面点击到该页的

http://localhost:8080/workshop/hello.htm

事实上,我们完全可以扩展SetLoggingContextValve,以实现更多的功能,例如:在日志中显示当前登录的用户名。SetLoggingContextValve使日志信息变得对我们更有意义。
3.SetLocaleValve
SetLocaleValve被用来确定请求的输入/输出字符集编码、地域信息。
<valve class="com.alibaba.turbine.pipeline.SetLocaleValve" defaultLocale="zh_CN" defaultCharset="GBK"/>
    地域信息(Locale) —— 会影响ResourceBundle、表单出错提示信息、邮件生成等和Locale相关的组件的执行结果。在上面的配置中,默认将采用zh_CN(即中国大陆)作为当前请求的Locale。
    字符集编码(Charset) —— 用来作为输出页面时所用的编码(即Content Type中指定的charset,输出字符集编码),和解析表单或query string时所用的编码(即输入字符集编码)。在上面的配置上,默认使用“GBK”作为字符集编码。
在正常情况下,浏览器会用Content Type中指定的charset来提交表单,因此输入、输出编码是一致的。然而,有些情况下,输入、输出编码会不一致。比如,通过外部系统提交的信息(如 贸易通、旺旺、合作网站),其字符集编码未必和网站一致。SetLocaleValve提供了一种特别的参数,来处理这种特殊情况。
SetLocaleValve的处理逻辑是这样的:
1.    一般情况下,SetLocaleValve将根据其配置中的defaultLocale和defaultCharset来确定当前locale和charset。该charset将被用于解析参数和输出页面。
2.    如果URL中包含类似这样的参数:_lang=zh_HK:UTF-8,这意味着用户要求进入中国香港版(zh_HK)网页,并使用UTF-8作为默认 的charset。SetLocaleValve会把这个值保存在cookie或session中。这样后继的页面不用指定_lang参数,也会使用 (zh_HK,UTF-8)地域和字符集编码。
3.    如果URL中包含参数:_lang=default,这意味着用户要求回到系统默认的locale和charset,即valve参数中所指定的defaultLocale和defaultCharset。在本例中,分别为zh_CN和GBK。
4.    如果URL中包含参数:_input_charset=UTF-8,这意味着必须用UTF-8来解码参数。需要注意的是,这个值可能和valve配置中 指定的defaultCharset不同。SetLocaleValve将仍然以前面所说的规则来确定页面的输出字符集编码。外部系统(如贸易通、旺旺、 合作网站)通过HTTP方式向系统发送请求时,最好指定该参数,而不是假设网站的默认charset。这样,当网站的默认charset发生改变时,也不 会影响到外部系统。
4.AnalyzeURLValve
AnalyzeURLValve用来分析URL的成分。
<valve class="com.alibaba.turbine.pipeline.AnalyzeURLValve"/>
有 不少初学者误以为URL和物理文件之间是一一对应的。例如:http://localhost:8080/workshop/hello.htm代表 workshop目录下有一个hello.htm文件。这是不一定的。事实上,你应该把URL看作一个参数。一个URL实际对应的操作是什么,可以由应用 程序自己来决定。
以http://localhost:8080/workshop/hello.htm为例,URL的分析分为下面几步:
1.    /workshop被称为Context Path。应用服务器看到/workshop,就把这个请求交给workshop应用来接管。
2.    /hello.htm被称为Servlet Path。因为我们在web.xml中把*.htm映射到Webx Controller Servlet,所以Webx Controller Servlet就接管了这个请求。
3.    Webx Controller Servlet激活pipeline,继而调用AnalyzeURLValve来分析/hello.htm是什么意思。根据webx默认的映射规则,/hello.htm被转换成/hello.vm。
经过上述转换,最后得到的结果被称为target。在这个例子中,target的值为/hello.vm。
有人想当然认为/hello.vm是代表Velocity模板,事实上,这也是不一定的。到目前为止,只能称它为“target”。在后续的valve中,我们会将/hello.vm解释为Velocity模板。
5.ChooseValve
ChooseValve用来作条件分支。
<valve class="com.alibaba.turbine.pipeline.ChooseValve">
    <when extension="jsp, vm"></when>
    <when extension="do"></when>
    <otherwise></otherwise>
</valve>
当<when>中的条件被满足时,这个when所包含的分支就被执行。如果所有when都不满足,就执行otherwise分支。
<when>标签支持多种条件:
1.    如果target的后缀为jsp或vm,那么就执行该分支。
<when extension="jsp, vm">
2.    如果target的值以/images/开头,那么就执行分支。
<when target="/images/**">
3.    如果path的值以/member/images/开头,那么就执行分支。和target不同,path是URL去掉contextPath之后的剩余部分,而target是由AnalyseURLValve翻译而来的。
<when path="/member/images/**">
在我们的例子中,/hello.vm匹配了<when extension=”jsp,vm”>分支。在这个分支里,/hello.vm被视作screen模板,并作进一步处理。
6.PerformActionValve
PerformActionValve用来执行action。
<valve class="com.alibaba.turbine.pipeline.PerformActionValve" actionParam="action"/>
参数actionParam的值指明了valve将根据什么参数来确定action的名称。
例如,假如URL为http://localhost:8080/workshop/hello.htm?action=myAction,那么valve将会执行MyAction类。
有关action的细节,我们将在后面展现。
7.PerformScreenTemplateValve
PerformScreenTemplateValve用来执行screen和模板。
<valve class="com.alibaba.turbine.pipeline.PerformScreenTemplateValve"/>
假如target的值为:/xxx/yyy/hello.vm,那么,valve会:
1.    在/templates/screen目录下,找到/xxx/yyy/hello.vm模板。
2.    依次查找screen类:
a)    xxx.yyy.Hello        (如果找不到,尝试下一个)
b)    xxx.yyy.Default    (如果找不到,尝试下一个)
c)    xxx.Default        (如果找不到,尝试下一个)
d)    Default            (如果找不到,尝试下一个)
e)    TemplateScreen    (系统默认screen,不可能找不到的)
3.    执行screen,并渲染screen模板。
4.    如果存在layout布局,则渲染layout,并将screen放于布局中。
关于layout布局,后面会讲到。
8.PerformScreenValve
和前一个valve不同,PerformScreenValve不关心模板,只关心screen类。
<valve class="com.alibaba.turbine.pipeline.PerformScreenValve"/>
假如target的值为:/xxx/yyy/hello.do,那么,valve会:
1.    查找screen类:xxx.yyy.Hello
2.    执行screen类。
3.    如果存在layout布局,则渲染layout,并将screen放于布局中。

9.RedirectTargetValve
<valve class="……" label="myLabel">
<valve class="com.alibaba.turbine.pipeline.RedirectTargetValve" goto="myLabel"/>
RedirectTargetValve实现了内部重定向。
如 果在screen、action里调用了rundata.setRedirectTarget(),那么,RedirectTargetValve会将控 制转向到标记了label的valve上,从而实现内部的循环。在上面的例子中,PerformActionValve和 PerformScreenTemplateValve(或PerformScreenValve)被重新执行了,但target的值因 setRedirectTarget()而改变,因而实现了内部重定向。
10.SetErrorPageValve
<valve class="com.alibaba.turbine.pipeline.SetErrorPageValve" target="error.vm"/>
SetErrorPageValve的工作是显示错误页面error.vm。

 URL的生成与解析

String viewNewUser = "http://localhost:8080/workshop/view_user.htm?id=" + user.getId() +"&new_user=true";
rundata.setRedirectLocation(viewNewUser);

这种硬编码的URL有很多缺点:
    不能应付环境的变化:hostname、端口、context path(/workshop)、参数等都有可能变化。一但变化,就要修改代码并重新编译。
    难以组装参数:通过字符串的拼接来组装参数十分笨拙。特别是当参数的值包括一些特殊字符时,很容易忘记对其进行URL encoding,转换成%20%3C之类的形式。
    没有一个集中的地方控制URL。当一个URL被改变时,需要修改程序 —— 很容易漏掉一些程序。
在Webx中,有一个专门用来生成URL的服务:URI Broker Service,能够解决这些问题。下面,让我们利用URI Broker Service来改进应用。
1.配置URIBrokerService

1.1.修改webx.xml

请修改webx.xml,增加URIBrokerService的配置:

<configuration>
    <services>
        <servicename="URIBrokerService">
            <propertyname="uri.descriptors">
                <value>/WEB-INF/uri.xml</value>
            </property>
        </service>
    </services>
</configuration>

1.2.创建uri.xml

为了使用URI Broker Service,你需要创建一个简单的URI配置文件:/WEB-INF/uri.xml。内容如下:

<?xmlversion="1.0" encoding="GB2312"?>
<uri-config>
    <!-- 外部链接。-->
    <uriname="alibabaSite"expose="true">
        <serverURI>http://china.alibaba.com/</serverURI>
    </uri>
    <!--内部链接-->
    <turbine-uriname="workshopModule"expose="true"/>
    <turbine-content-uriname="workshopContent"expose="true"extends="workshopModule"/>
    <turbine-uriname="viewNewUser"expose="true"extends="workshopModule">
        <target>viewUser.vm</target>
        <queryid="newUser">true</query>
    </turbine-uri>
</uri-config>

这个文件定义了几个URI:alibabaSite、workshopModule、workshopContent、viewNewUser。如果有不明白的,我们一会儿再解释。

2.在程序中使用URIBrokerService

接下来,请修改SiteUserAction类,使之使用URIBrokerService来生成URL。

publicabstractclass SiteUserAction extends TemplateAction {
    protectedabstractURIBrokerService getURIBrokerService();
    publicvoiddoRegister(RunData rundata, TemplateContext context)
            throws WebxException {
       URIBroker broker = getURIBrokerService().getURIBroker("viewNewUser", rundata);
       broker.addQueryData("id",user.getId());
        StringviewNewUser = broker.render();
        rundata.setRedirectLocation(viewNewUser);
    }
}

在程序中使用URIBrokerService分成以下几个步骤:

2.1.        取得URIBrokerService的实例。在Action/screen/control中,可以用“注入”的方式来取得:

protectedabstract URIBrokerService getURIBrokerService();

2.2.        URIBrokerService中,取得URIBroker实例。

URIBroker broker = getURIBrokerService().getURIBroker("viewNewUser", rundata);

其中,"viewNewUser"就是uri.xml中定义的一个URL

<turbine-uriname="viewNewUser"expose="true"extends="workshopModule">
    <target>viewUser.vm</target>
    <queryid="newUser">true</query>
</turbine-uri>

 <turbine-uri>定义了一个TurbineURIBroker类的实例。

 <turbine-uri extends="workshopModule">指明该URLworkshopModule URL继承所有的属性,包括hostnameportcontext path等等。因此当前URL将在此基础上被继续构造:http://localhost:8080/workshop

 <target>viewUser.vm</target>定义了target=viewUser.vm。该target将被追加到URL的末尾:/view_user.htm

 <query id="newUser">true</query>定义了一个参数newUser=true。该参数将被加入到URL的末尾:?newUser=true

因此,当broker对象从URIBrokerService中被取出时,就已经代表了如下的URLhttp://localhost:8080/workshop/view_user.htm?newUser=true。最重要的是,作为程序代码,它并不关心URL长成什么样子。

2.3.        追加ID参数。

broker.addQueryData("id",user.getId());

以这种方式追加的参数,你不用担心其中包含了URL禁止使用的字符,如空格、加号、&号等等。URIBroker会自动对所有参数进行URL encoding编码,将禁用字符转换成%20%3C之类的形式。

2.4.        生成最终URL

String viewNewUser = broker.render();

这样就生成了最终的URLhttp://localhost:8080/workshop/view_user.htm?newUser=true&id=baobao

3.在模板中使用URIBrokerService

在模板中使用URIBrokerService比在程序中使用更简单——你可以直接访问在uri.xml中被标记成expose=true的所有URL定义。

下面,让我们来试一下:

i.        header.vm control中,创建一张logo图片。

ii.        menu.vm control中,创建一个指向Alibaba中文站的链接。

iii.        viewUser.vm screen中,创建一个用来修改用户信息的按钮。

3.1.创建logo图片

请在webroot/目录下创建一个images子目录,并在其中创建一个图片:logo_webx.png

Webx框架之服务简介 - 和申 - 和申的个人主页

3.2.修改control模板:header.vm

请修改templates/control/header.vm

<img src="http://1985wanggang.blog.163.com/blog/$workshopContent.setContentPath("images/logo_webx.png")"/>
<h1>标题、Logo</h1>

uri.xml中,workshopContent定义了一个<turbine-content-uri>(即TurbineContentURIBroker)对象。利用该URL,可以创建指向WEB应用目录下的资源,例如图片、CSSJavaScript等。

由于我们将workshopContent设置成了expose=true,因此可以在模板中直接引用$workshopContent对象。

3.3.修改control模板:menu.vm

请修改templates/control/menu.vm

<ul>
  <li><a href="$alibabaSite">阿里巴巴中文站</a></li>
  <li>菜单</li>
  <li>菜单</li>
  <li>菜单</li>
  <li>菜单</li>
</ul>

uri.xml中,alibabaSite定义了一个<uri>(即URIBroker)对象。

3.4.修改screen模板:viewUser.vm

请修改templates/screen/viewUser.vm

<hr/><input type="button" value=" 修 改"  onclick="location='$workshopModule.setTarget("modifyUser.vm").addQueryData("id", $user.id)'"/>

uri.xml中,workshopModule定义了一个<turbine-uri>(即TurbineURIBroker)对象。这个URL通常用来指定应用程序的页面。例如,当你点击上面的HTML所创建的按钮时,页面将会跳转到:http://localhost:8080/workshop/modify_user.htm?id=baobao

那么,modify_user.htm代表什么呢?是的,这个页面我们还没有实现。我们将把实现这个页面的工作留给你,作为练习。

在这个例子中,我们在模板中尝试了好几种URL类型,分别用来生成:外部网站的URL、应用内资源的URL、应用内页面的URLURIBroker使得所有的URL都被集中管理在uri.xml文件中——无论是页面还是程序代码,都不需要了解URL生成的具体细节:hostnameportcontext path等。它们要做的,只是把一些参数填进去,URIBroker将会负责生成完整的URL

  评论这张
 
阅读(2288)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2016