3.Webx基础层次之Webx Framework简介
Webx是一套基于Java Servlet API的通用Web框架。整个Webx框架分成三个层次,本章将简单介绍其第二个层次:Webx Framework。事实上,这是第一个真正涉足WEB技术的层次。前一个层次SpringExt只是提供了一个通用的扩展机制。
Webx Framework负责完成一系列基础性的任务,如下表所示:
表 3.1. Webx Framework的任务
系统初始化 | 响应请求 |
---|---|
初始化Spring容器 | 增强request/response/session的功能 |
初始化日志系统 | 提供pipeline流程处理机制 |
异常处理 | |
开发模式 |
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 Framework将会自动搜索/WEB-INF
目录下的XML配置文件,并创建下面这种级联的spring容器。
如图所示。Webx Framework将一个WEB应用分解成多个小应用模块:app1
、app2
,当然名字可以任意取。
每个小应用模块独享一个Spring Sub Context子容器。两个子容器之间的beans无法互相注入。
所有小应用模块共享一个Spring Root Context根容器。根容器中的bean可被注入到子容器的bean中;反之不可以。
将一个大的应用分解成若干个小应用模块,并使它们的配置文件相对独立,这是一种很不错的开发实践。然而,如果你的应用确实很简单,你不希望把你的应用分成多个小应用模块,那么,你还是需要配置至少一个小应用模块(子容器)。
每个现代的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>
LogConfiguratorListener
会根据你当前应用所依赖的日志系统(通常配置在maven project中),来自动选择合适的日志配置文件。
假设你的应用依赖了logback的jar包,那么listener就会查找
/WEB-INF/logback.xml
,并用它来初始化logback;如果你的应用依赖了log4j的jar包,那么listener也会很聪明地查找
/WEB-INF/log4j.xml
配置文件。假如以上配置文件不存在,listener会使用默认的配置 —— 把日志打印在控制台上。
Listener支持对配置文件中的placeholders进行替换。
Listener支持同时初始化多种日志系统。
注意 | |
---|---|
有关日志系统的使用方法,另有文档详细讲述。 |
当Webx Framework接收到一个来自WEB的请求以后,实际上它主要做了两件事:
首先,它会增强request、response、session的功能,并把它们打包成更易使用的
RequestContext
对象。其次,它会调用相应子应用的pipeline,用它来做进一步的处理。
假如在上面的过程中出现异常,则会触发Webx Framework处理异常的过程。
此外,Webx Framework还提供了一组辅助开发的功能,例如查看环境变量,查看schema等。这些功能只在开发模式生效,生产模式下自动关闭。
Webx Framework提供了一个request contexts服务。Request contexts服务利用HttpServletRequestWrapper
和HttpServletResponseWrapper
对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的应用,都将获得新功能:
再比如,只要你配置了upload服务,那么下面的调用将同样适用于multipart/form-data
类型的请求(Servlet API本身是不支持upload表单的):
注意 | |
---|---|
有关Request Contexts的原理和使用方法的详情,请参阅第 6 章 Filter、Request Contexts和Pipeline。 |
表 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指南。 |
在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的对象中。但你可以把HttpServletRequest
、HttpServletResponse
和HttpSession
对象注入到singleton对象中。为什么呢?原来,Request contexts服务对这几个常用对象进行了特殊处理,将它们转化成了singleton对象。
如果没有这个功能,那么我们就不得不将上例中的LoginAction
配置成request
scope。这增加了系统的复杂性,也成倍地降低了性能。而将LoginAction
设置成singleton,只需要在系统启动时初始化一次,以后就可以快速引用它。
Webx Framework赋予开发者极大的自由,来定制处理请求的流程。这种机制就是pipeline。
Pipeline的意思是管道,管道中有许多阀门(Valve),阀门可以控制水流的走向。Webx Framework中的pipeline可以控制处理请求的流程的走向。如图所示。
Webx Framework并没有规定管道的内容 —— 定制管道是应用开发者的自由。然而Webx Framework提供了一系列通用valves,你可以使用它们:
表 3.3. 通用valves
分类 | Valves | 说明 |
---|---|---|
循环 | <while> |
有条件循环 |
<loop> |
无条件循环 | |
选择分支 | <if> |
单分支 |
<choose><when><otherwise> |
多分支 | |
中断 | <break> |
无条件中断 |
|
有条件中断 | |
<exit> |
无条件退出整个pipeline(结束所有的嵌套层次) | |
异常捕获 | <try-catch-finally> |
类似Java中的try-catch-finally结构 |
嵌套 | <sub-pipeline> |
创建嵌套的子pipeline。 |
注意 | |
---|---|
有关Pipeline的原理和使用方法的详情,请参阅第 6 章 Filter、Request Contexts和Pipeline。 |
当应用发生异常时,Webx Framework可以处理这些异常。
Webx Framework提供了一个开关,可以让应用运行于“生产模式(Production Mode)”或是“开发模式(Development Mode)” 。
在开发模式下,会有一系列不同于生产模式的行为。
-
不同的主页 —— 在开发模式的主页中,可以查看和查询系统内部的信息。
-
不同的详细出错页面。
-
开发模式下,可展示所有可用的schemas。
-
开发模式下,可以查阅容器内部的信息。
可供查阅的信息包括:
表 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
当一个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>
为什么使用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加在excludes
或passthru
参数里。这样,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
接到请求以后,就会调用WebxRootController
。从这里开始,进入Spring的世界 —— 此后所有的对象:WebxRootController
、WebxController
、RequestContext
、Pipeline
等,全部是通过SpringExt配置在Spring Context中的。
WebxRootController
对象存在于root context中,它被所有子应用所共享。它会创建RequestContext
实例 —— 从而增强request、response、session的功能。接下来,WebxController
对象会被调用。
WebxController
对象是由每个子应用独享的,子应用app1
和app2
可以有不同的WebxController
实现。默认的实现,会调用pipeline。
Pipeline也是由各子应用自己来配置的。假如pipeline碰到无法处理的请求,如静态页面、图片等,pipeline应当执行<exit/>
valve强制退出。然后WebxRootController
就会“放弃控制” —— 这意味着request将被返还给/WEB-INF/web.xml
中定义的servlet、filter或者返还给servlet engine本身来处理。
WebxRootController
是被所有子应用所共享的逻辑。 假如你想创建一种新的WEB框架,可以自己定义一个新的WebxRootController
的实现。这个方案非常适合作为一个新Web框架的起点。
WebxController
是用来控制子应用的。每个子应用可以拥有不同的WebxController
实现。
Webx Framework默认的WebxController
是调用pipeline。假如你不想用pipeline,而希望实现自己的针对子应用的逻辑,那么最简单的方法就是实现自己的WebxController
或者扩展AbstractWebxController
。