第 3 章 Webx Framework

3.1. Webx的初始化
3.1.1. 初始化级联的Spring容器
3.1.2. 初始化日志系统
3.2. Webx响应请求
3.2.1. 增强request、response、session的功能
3.2.2. Pipeline流程机制
3.2.3. 异常处理机制
3.2.4. 开发模式工具
3.2.5. 响应和处理请求的更多细节
3.3. 定制Webx Framework
3.3.1. 定制WebxRootController
3.3.2. 定制WebxController
3.4. 本章总结

Webx是一套基于Java Servlet API的通用Web框架。整个Webx框架分成三个层次,本章将简单介绍其第二个层次:Webx Framework。事实上,这是第一个真正涉足WEB技术的层次。前一个层次SpringExt只是提供了一个通用的扩展机制。

Webx Framework负责完成一系列基础性的任务,如下表所示:

表 3.1. Webx Framework的任务

系统初始化响应请求
初始化Spring容器增强request/response/session的功能
初始化日志系统提供pipeline流程处理机制
 异常处理
 开发模式

本章不会涉及太深的细节,如果你想了解更多,请参考其它文档。

3.1. Webx的初始化

3.1.1. 初始化级联的Spring容器

Webx Framework将负责创建一组级联的Spring容器结构。Webx所创建的Spring容器完全兼容于Spring MVC所创建的容器,可被所有使用Spring框架作为基础的WEB框架所使用。

例 3.1. 初始化Spring容器 - /WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/j2ee  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
    ">
    ...
    <listener>
        <listener-class>com.alibaba.citrus.webx.context.WebxContextLoaderListener</listener-class> 
    </listener>
    ...
</web-app>

Webx利用WebxContextLoaderListener来初始化Spring,用来取代Spring的ContextLoaderListener。事实上,前者是从后者派生的。

Webx Framework将会自动搜索/WEB-INF目录下的XML配置文件,并创建下面这种级联的spring容器。

级联的Spring容器

图 3.1. 级联的Spring容器

如图所示。Webx Framework将一个WEB应用分解成多个小应用模块:app1app2,当然名字可以任意取。

  • 每个小应用模块独享一个Spring Sub Context子容器。两个子容器之间的beans无法互相注入。

  • 所有小应用模块共享一个Spring Root Context根容器。根容器中的bean可被注入到子容器的bean中;反之不可以。

将一个大的应用分解成若干个小应用模块,并使它们的配置文件相对独立,这是一种很不错的开发实践。然而,如果你的应用确实很简单,你不希望把你的应用分成多个小应用模块,那么,你还是需要配置至少一个小应用模块(子容器)。

3.1.2. 初始化日志系统

每个现代的WEB应用,都需要日志系统。流行的日志系统包括Log4j、Logback。

Webx Framework使用SLF4J作为它的日志框架。因此Webx Framework理论上支持所有日志系统。然而目前为止,它只包含了log4j和logback这两种日志系统的初始化模块(如有需要,可以扩充)。初始化日志系统很简单。

例 3.2. 初始化日志系统 - /WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/j2ee  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
    ">
    ...
    <listener>
        <listener-class>com.alibaba.citrus.logconfig.LogConfiguratorListener</listener-class> 
    </listener>
    ...
</web-app>

Webx利用LogConfiguratorListener来初始化日志系统。

LogConfiguratorListener会根据你当前应用所依赖的日志系统(通常配置在maven project中),来自动选择合适的日志配置文件。

  • 假设你的应用依赖了logback的jar包,那么listener就会查找/WEB-INF/logback.xml,并用它来初始化logback;

  • 如果你的应用依赖了log4j的jar包,那么listener也会很聪明地查找/WEB-INF/log4j.xml配置文件。

  • 假如以上配置文件不存在,listener会使用默认的配置 —— 把日志打印在控制台上。

  • Listener支持对配置文件中的placeholders进行替换。

  • Listener支持同时初始化多种日志系统。

[注意]注意

有关日志系统的使用方法,另有文档详细讲述。

3.2. Webx响应请求

Webx Framework如何响应请求

图 3.2. Webx Framework如何响应请求

当Webx Framework接收到一个来自WEB的请求以后,实际上它主要做了两件事:

  1. 首先,它会增强request、response、session的功能,并把它们打包成更易使用的RequestContext对象。

  2. 其次,它会调用相应子应用的pipeline,用它来做进一步的处理。

  3. 假如在上面的过程中出现异常,则会触发Webx Framework处理异常的过程。

此外,Webx Framework还提供了一组辅助开发的功能,例如查看环境变量,查看schema等。这些功能只在开发模式生效,生产模式下自动关闭。

3.2.1. 增强request、response、session的功能

Webx Framework提供了一个request contexts服务。Request contexts服务利用HttpServletRequestWrapperHttpServletResponseWrapper对request和response对象进行包装,以实现新的功能。

一个基本的request contexts的配置看起来是下面的样子:

例 3.3. 配置request contexts服务

<services:request-contexts xmlns="http://www.alibaba.com/schema/services/request-contexts">
    <basic />
    <buffered />
    <lazy-commit />
    <parser />
    <set-locale defaultLocale="zh_CN" defaultCharset="UTF-8" />
    ...
</services:request-contexts>

<services:upload sizeMax="5M" />

Request contexts所有的功能都是可配置、可扩展的 —— 它是基于SpringExt的扩展机制。

Request contexts所增加的功能对于所有的基于标准Servlet API的应用都是透明的 —— 这些应用根本不需要知道这些扩展的存在。例如,假如你在request contexts服务中配置了增强的session框架,那么所有通过标准的Servlet API取得session的应用,都将获得新功能:

例 3.4. 取得增强的session对象

HttpSession session = request.getSession();

再比如,只要你配置了upload服务,那么下面的调用将同样适用于multipart/form-data类型的请求(Servlet API本身是不支持upload表单的):

例 3.5. 取得upload表单的参数

String value = request.getParameter("myparam");
[注意]注意

有关Request Contexts的原理和使用方法的详情,请参阅第 6 章 Filter、Request Contexts和Pipeline

3.2.1.1. Request contexts中可用的功能

表 3.2. 可用的RequestContext扩展

名称说明
<basic>对输入、输出的数据进行安全检查,排除可能的攻击。例如:XSS过滤、CRLF换行回车过滤等。
<buffered>对写入response中的数据进行缓存,以便于实现嵌套的页面。
<lazy-commit>延迟提交response,用来支持基于cookie的session。
<parser>解析用户提交的参数,无论是普通的请求,还是multipart/form-data这样的用于上传文件的请求。
<set-locale>设置当前请求的区域(locale)、编码字符集(charset)。
<rewrite>改写URL及参数,类似于Apache HTTPD Server中的rewrite模块。
<session>增强的Session框架,可将session中的对象保存到cookie、数据库或其它存储中。
[注意]注意

有关以上所有Request Contexts的详情,请参阅第 7 章 Request Contexts功能指南第 8 章 Request Context之Session指南

3.2.1.2. 注入特殊对象

在Webx中,你可以这样做,例如:

例 3.6. 注入request、response、session

public class LoginAction {
    @Autowired
    private HttpServletRequest request;

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private HttpSession session;
    ...
}

在这个例子中,LoginAction类可以是一个singleton。一般来说,你不能把request scope的对象,注入到singleton scope的对象中。但你可以把HttpServletRequestHttpServletResponseHttpSession对象注入到singleton对象中。为什么呢?原来,Request contexts服务对这几个常用对象进行了特殊处理,将它们转化成了singleton对象。

如果没有这个功能,那么我们就不得不将上例中的LoginAction配置成request scope。这增加了系统的复杂性,也成倍地降低了性能。而将LoginAction设置成singleton,只需要在系统启动时初始化一次,以后就可以快速引用它。

3.2.2. Pipeline流程机制

Webx Framework赋予开发者极大的自由,来定制处理请求的流程。这种机制就是pipeline。

Pipeline的意思是管道,管道中有许多阀门(Valve),阀门可以控制水流的走向。Webx Framework中的pipeline可以控制处理请求的流程的走向。如图所示。

Pipeline工作原理示意

图 3.3. Pipeline工作原理示意

Webx Framework并没有规定管道的内容 —— 定制管道是应用开发者的自由。然而Webx Framework提供了一系列通用valves,你可以使用它们:

表 3.3. 通用valves

分类Valves说明
循环<while>有条件循环
<loop>无条件循环
选择分支<if>单分支
<choose><when><otherwise>多分支
中断<break>无条件中断

<break-if>

<break-unless>

有条件中断
<exit>无条件退出整个pipeline(结束所有的嵌套层次)
异常捕获<try-catch-finally>类似Java中的try-catch-finally结构
嵌套<sub-pipeline>创建嵌套的子pipeline。
[注意]注意

有关Pipeline的原理和使用方法的详情,请参阅第 6 章 Filter、Request Contexts和Pipeline

3.2.3. 异常处理机制

当应用发生异常时,Webx Framework可以处理这些异常。

表 3.4. Webx如何处理异常

条件处理
开发模式展示详细出错信息。
生产模式假如存在exception pipeline用exception pipeline来处理异常;
不存在exception pipeline显示web.xml中定义的默认错误页面。

3.2.4. 开发模式工具

Webx Framework提供了一个开关,可以让应用运行于“生产模式(Production Mode)”或是“开发模式(Development Mode)” 。

例 3.7. 配置运行模式

<services:webx-configuration>
    <services:productionMode>${productionMode:true}</services:productionMode> 
</services:webx-configuration>

使用这行配置,并且在启动应用服务器时指定参数“-DproductionMode=false”,就会让Webx以开发模式启动。

在开发模式下,会有一系列不同于生产模式的行为。

  • 不同的主页 —— 在开发模式的主页中,可以查看和查询系统内部的信息。

    开发模式的主页

    图 3.4. 开发模式的主页

  • 不同的详细出错页面。

    开发模式的详细出错页面

    图 3.5. 开发模式的详细出错页面

  • 开发模式下,可展示所有可用的schemas。

    开发模式下展示所有可用的schemas

    图 3.6. 开发模式下展示所有可用的schemas

  • 开发模式下,可以查阅容器内部的信息。

    开发模式下查阅容器内部的信息

    图 3.7. 开发模式下查阅容器内部的信息

    可供查阅的信息包括:

    表 3.5. 开发模式中可供查阅的容器信息

    名称说明
    Beans

    查看各Spring容器中的全部bean的定义。

    这个工具有助于开发者理解用schema所定义的services和spring beans之间的联系。

    Configurations

    查看用来创建各Spring容器的配置文件。

    这个工具会以树状和语法高亮显示配置文件以及所有被import的配置文件的内容。

    不同于Beans工具,Configurations工具只忠实地展现配置文件的内容。而Beans工具展现的是真实的Beans结构。

    Resolvable Dependencies

    查看所有由框架置入到容器中的对象,例如:HttpServletRequest对象。这些对象不需要在配置文件中定义,就可被注入到应用中。

    Resources

    跟踪Resources的装载过程,显示Resources的树状结构。

    这个工具有助于开发者理解ResourceLoadingService的工作原理。

    URIs

    查看所有的URI brokers。

    Pull Tools

    查看所有模板中可用的pull tools。

事实上,Webx Framework提供了一套专用的内部框架,使你可以往开发模式中添加更多的开发工具。例如,创建下面的功能并非难事:

  • 查看session对象。

  • 提供各种编码、解码的工具,以方便开发、调试应用。例如:将UTF-8编码的字符串转换成GBK编码;或者将字符串进行URL escape编码、解码等。

Webx Framework提供了一个接口:ProductionModeAware。Spring context中的beans,如果实现了这个接口,就可以感知当前系统的运行模式,从而根据不同的模式选择不同的行为 —— 例如:在生产模式中打开cache,在开发模式中关闭cache。

例 3.8. 利用ProductionModeAware接口感知运行模式,并自动开关cache

public class ModuleLoaderServiceImpl implements ProductionModeAware {  
    public void setProductionMode(boolean productionMode) { 
        this.productionMode = productionMode;
    }

    @Override
    protected void init() {
        ……
        if (cacheEnabled == null) {
            cacheEnabled = productionMode; 
        }
        ……
    }
}

实现ProductionModeAware接口。

根据当前运行模式自动开关cache。

3.2.5. 响应和处理请求的更多细节

当一个HTTP请求到达时,首先由WebxFrameworkFilter接手这个请求:

例 3.9. 配置WebxFrameworkFilter - /WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/j2ee  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
    ">
    ...
    <filter>
        <filter-name>webx</filter-name>
        <filter-class>com.alibaba.citrus.webx.servlet.WebxFrameworkFilter</filter-class> 
        <init-param>
            <param-name>excludes</param-name>
            <param-value><!-- 需要被“排除”的URL路径,以逗号分隔,前缀!表示“包含”。例如/static, *.jpg, !/uploads/*.jpg --></param-value>  
        </init-param>
        <init-param>
            <param-name>passthru</param-name>
            <param-value><!-- 需要被“略过”的URL路径,以逗号分隔,前缀!表示“不要略过”。例如/myservlet, *.jsp --></param-value>  
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>webx</filter-name>
        <url-pattern>/*</url-pattern>  
    </filter-mapping>
    ...
</web-app>

定义WebxFrameworkFilter

可选的参数:“排除”指定名称的path,以逗号分隔,例如:/static, *.jpg。如果路径以!开始,表示“不排除”特殊目录。例如:*.jpg, !/uploads/*.jpg表示排除所有JPG图像文件,但不排除/uploads目录下的JPG图像文件。

可选的参数:“略过”指定名称的path,以逗号分隔,例如:/myservlet, *.jsp。和excludes参数一样,也支持!前缀,表示“不要略过”特殊目录。

匹配所有的path。

为什么使用filter而不是servlet呢?传统的WEB框架的控制器一般都是用servlet实现的。原因是:

  • Filter可以“返还控制” —— 上面的配置文件直接把“/*”映射到webx filter中,这意味着webx接管了这个应用的所有请求。静态页面和资源怎么办?没关系,如果webx发现这个请求不应该由webx来处理,就会把控制“返还”给原来的控制器 —— 可能是另一个filter、servlet或者返回给servlet引擎,以默认的方式来处理。而Servlet是不具备“返还控制”的机制的。

  • Servlet/Filter mapping的局限性 —— 标准的servlet引擎将URL映射到filter或servlet时,只支持前缀映射和后缀映射两种方式,非常局限。而实际情况往往复杂得多。Webx建议将所有请求都映射给webx来处理,让webx对请求做更灵活的映射。

如果你的web.xml中还有一些其它的servlet mappings,为了避免和Webx的URL起冲突,你可以把这些mapping加在excludespassthru参数里。这样,WebxFrameworkFilter就会排除或略过指定的URL。例如:

        <init-param>
            <param-name>excludes</param-name>
            <param-value>/static, *.jpg, !/uploads/*.jpg</param-value>
        </init-param>
        <init-param>
            <param-name>passthru</param-name>
            <param-value>/myservlet, *.jsp</param-value>
        </init-param>

passthru略过”和“excludes排除”的区别在于,如果一个servlet或filter接手被webx passthru的请求时,它们还是可以访问到webx的部分服务,包括:

  • RequestContext服务,例如:解析参数、解析upload请求、重写请求、设置字符集编码和区域、基于cookie的session等。

  • 开发模式及工具。

  • 异常处理。

  • 共享webx的spring容器。

也就是说,对于一个被passthru的请求,webx的行为更像是一个普通的filter。而“排除”则不同,如果一个请求被“排除”,webx将会立即放弃控制,将请求交还给服务器。接手控制的servlet或filter将无法访问webx一切的服务。

下图是WebxFrameworkFilter处理一个WEB请求的过程。

WebxFrameworkFilter处理请求的详细过程

图 3.8. WebxFrameworkFilter处理请求的详细过程

如图所示,WebxFrameworkFilter接到请求以后,就会调用WebxRootController。从这里开始,进入Spring的世界 —— 此后所有的对象:WebxRootControllerWebxControllerRequestContextPipeline等,全部是通过SpringExt配置在Spring Context中的。

WebxRootController对象存在于root context中,它被所有子应用所共享。它会创建RequestContext实例 —— 从而增强request、response、session的功能。接下来,WebxController对象会被调用。

WebxController对象是由每个子应用独享的,子应用app1app2可以有不同的WebxController实现。默认的实现,会调用pipeline。

Pipeline也是由各子应用自己来配置的。假如pipeline碰到无法处理的请求,如静态页面、图片等,pipeline应当执行<exit/> valve强制退出。然后WebxRootController就会“放弃控制” —— 这意味着request将被返还给/WEB-INF/web.xml中定义的servlet、filter或者返还给servlet engine本身来处理。

3.3. 定制Webx Framework

3.3.1. 定制WebxRootController

WebxRootController是被所有子应用所共享的逻辑。 假如你想创建一种新的WEB框架,可以自己定义一个新的WebxRootController的实现。这个方案非常适合作为一个新Web框架的起点。

例 3.10. 自定义WebxRootController

<webx-configuration xmlns="http://www.alibaba.com/schema/services">
    <components>
        <rootController class="com.myframework.MyRootController" /> 
    </components>
</webx-configuration>

创建自己的WebxRootController。最简便的方法是:扩展AbstractWebxRootController,免去了创建Servlet/Filter、初始化Spring容器、处理request、response等繁杂事务,并且完全支持SpringExt的所有功能,此外还包含了错误处理、开发模式等Webx Framework中的一切便利。。

3.3.2. 定制WebxController

WebxController是用来控制子应用的。每个子应用可以拥有不同的WebxController实现。

Webx Framework默认的WebxController是调用pipeline。假如你不想用pipeline,而希望实现自己的针对子应用的逻辑,那么最简单的方法就是实现自己的WebxController或者扩展AbstractWebxController

例 3.11. 自定义WebxController

<webx-configuration xmlns="http://www.alibaba.com/schema/services">
    <components defaultControllerClass="com.myframework.MyController"> 
        <component name="app1">
            <controller class="com.myframework.MyController" /> 
        </component>
    </components>
</webx-configuration>

指定默认的WebxController实现类。

对特定子应用明确指定WebxController实现类。

3.4. 本章总结

Webx Framework提供了一个可剪裁、可扩展的处理WEB请求基本框架。它所提供的基本功能事实上是每个WEB框架都需要用到的。Webx Framework为进一步实现WEB框架提供了坚实的基础。