http://b2b-doc.alibaba-inc.com/pages/viewpage.action?pageId=52214442
webx请求处理过程
在web.xml配置主控制器
com.alibaba.webx.WebxControllerServlet,映射对应的请求类型。对应的请求在其父类AbstractWebxControllerServlet的doGet中处理(doPost请求调用doGet同样处理),其处理过程如下:
经过以上几个步骤完成请求的响应。下面具体看下主要步骤
RunData组装过程
getRunData(request, response)的处理逻辑如下:
getWebxController()取得的是初始化创建的WebxLoader, getRunDataService()在WebxLoader 读取配置的时候在configureWebxController()这个步骤被创建进来的,通过以下代码获得RunDataService
其在webx-default.xml中配置如下:
即DefaultRunDataService,其getRunData代码如下:
super.getRunData(request, response, config) 如下:
getRequestContext在其父类的RequestContextChainServiceImpl中,其代码如下:
从上面代码可以看出,RunData是被一系列的RequestContextFactory使用
装饰模式层层包装创建出来的,其依次调用以下6个包装工厂的包装方法
BufferedRequestContextFactory
LazyCommitRequestContextFactory
ParserRequestContextFactory
SessionRequestContextFactory
SetLocaleRequestContextFactory
RunDataFactory
这些包装工厂加载过程详见"2.1.1 RequestContext包装过程".
通过调用RequestContextFactory
requestContext = factory.getRequestContextWrapper(requestContext);
获取requestContext,以下是各包装类对应实现及作用:
- BufferedRequestContextFactory 将requestContext包装成BufferedRequestContext(具体子类BufferedRequestContextImpl),对response输出流进行缓存操作;
- LazyCommitRequestContextFactory 将requestContext包装成LazyCommitRequestContext(具体子类LazyCommitRequestContextImpl),支持延迟提交response;
- ParserRequestContextFactory 将requestContext包装成ParserRequestContext(具体子类ParserRequestContextImpl),支持自动解析request parameters和cookie parameters,并透明地处理upload请求的request context实现;
- SessionRequestContextFactory将requestContext包装成SessionRequestContext(具体子类SessionRequestContextImpl),支持session,其覆盖getSession等方法;
- SetLocaleRequestContextFactory 将requestContext包装成SetLocaleRequestContext(具体子类SetLocaleRequestContextImpl),设置区域和编码字符集;
- RunDataFactory 将requestContext包装成RunData(具体子类由配置rundata.class对应值决定,这里是ChinaRunData),代表servlet运行时的信息;
创建完成后,依次调用requestContext.prepare()方法。
在后续可以向下转型,使用具体的包装,如RunData。这样设计有利于后续扩增。
RunData运行时数据结构如下图:
RequestContext包装过程
RequestContext包装类是被各自的RequestContextFactory创建出来的,各RequestContextFactory分别在DefaultRunDataService、RequestContextChainServiceImpl初始化。
首先看下DefaultRunDataService加载RequestContextFactory过程:
DefaultRunDataService首先判断是否兼容(rundata被升级成RequestContext的一个实现,所以在这打了个补丁),这里以配置文件中是否有request.${属性}.class键为依据。由于我们在RunDataService中配置
<property name="request.buffered.class" value="com.alibaba.webx.request.context.buffered.BufferedRequestContextFactory"/>
等属性,所以不作兼容处理。转而由配置文件中加载,可以看到RunDataService (webx-default.xml)配置的属性如下:
如果没有这些request.[name].class属性,则DefaultRunDataService初始化的时候会在configuration中设置以下属性
request.buffered.class : com.alibaba.webx.request.context.buffered.BufferedRequestContextFactory
request.lazycommit.class : com.alibaba.webx.request.context.lazycommit.LazyCommitRequestContextFactory
request.parser.class : com.alibaba.webx.request.context.parser.ParserRequestContextFactory
request.parser.autoupload : ...
request.parser.unescape.parameters : ...
request.parser.url.case.folding : ...
request.locale.class : com.alibaba.webx.request.context.locale.SetLocaleRequestContextFactory
下一步强制在configuration中增加rundata factory配置
request.rundata.class : ...RunDataFactory
且增加request.rundata.rundata.class,该键对应值从配置文件rundata.class中取得,如果没有配置这个属性,则默认取DefaultRunData,这里我们配置为
<property name="rundata.class" value="com.alibaba.china.common.webx.service.rundata.ChinaRunData"/>
所以最后键值对为
request.rundata.rundata.class : ...com.alibaba.china.common.webx.service.rundata.ChinaRunData
注意:
RunDataService在webx-turbine-default.xml(该配置在对应的scheme实现中,这里是turbine实现,因此在toolkit.webx.turbine-2.0.jar对应的com.alibaba.turbine.scheme包下可找到该配置文件,可参见"1.2 scheme的实现类加载过程")中配置如下:
<! - Webx核心service之一,用来保存每一个HTTP请求的状态。需要PoolService和UploadService的支持。->
<service name="RunDataService" class="com.alibaba.webx.service.rundata.DefaultRunDataService">
<property name="rundata.class" value="com.alibaba.turbine.service.rundata.DefaultRunData"/>
</service>
如果后面未进行配置覆盖,则默认的就是DefaultRunData。
最后调用父类RequestContextChainServiceImpl的初始化方法,其把这些键值设置到factories中,其最后值包括如下:
BufferedRequestContextFactory
LazyCommitRequestContextFactory
ParserRequestContextFactory
SessionRequestContextFactory
SetLocaleRequestContextFactory
RunDataFactory
请求处理过程 handleRequest
这里请求的处理过程是使用类链式的处理,spring、webwork、tomcat也使用类似的处理机制!webx中链式处理机制称为pipeline,该机制引用源代码中的注释解释如下:
pipeline: 代表一组顺序执行的操作,好象液体流过一根管道一样
_Valve: _ 代表pipeline中的一个"阀门" Valve这个词的意思就是真实世界中的水管中的阀门,它可以控制和改变液体的流向
pipeline中可以根据条件决定是否执行某步操作,典型的一个pipeline配置如下:
在webx-default.xml中配置一个pipeline服务如下:
<service name="PipelineService"
class="com.alibaba.service.pipeline.DefaultPipelineService">
<property name="pipeline.default.descriptor" value="elf/pipeline.xml" />
</service>
其中pipeline.default.descriptor指定了pipeline配置文件所在,其配置如下:
以上配置的在请求的处理过程中会按顺序执行,具体实现过程如下:
一般配置的value如下:
com.alibaba.service.pipeline.TryCatchFinallyValve
com.alibaba.service.pipeline.DefaultPipeline
com.alibaba.turbine.pipeline.SetLoggingContextValve
com.alibaba.turbine.pipeline.SetLocaleValve
com.alibaba.pivot.common.hessian.valve.HessianRemoteCallValve
com.alibaba.china.common.webx.valve.SessionValve
com.alibaba.turbine.pipeline.AnalyzeURLValve
com.alibaba.elf.web.common.SSOValve
com.alibaba.bopshared.valve.PermissionValve
com.alibaba.china.common.webx.valve.SecurityCheckValve
com.alibaba.turbine.pipeline.ChooseValve
com.alibaba.turbine.pipeline.RedirectTargetValve
com.alibaba.turbine.pipeline.SetLoggingContextValve
各Value的作用分别如下:
利用 NDC 和 MDC 的机制,简单快捷的为Web应用日志增加用户跟踪,具体可参考
http://www.ibm.com/developerworks/cn/web/wa-lo-usertrack/index.html
设置编码
<valve class="com.alibaba.turbine.pipeline.SetLocaleValve" defaultLocale="zh_CN" defaultCharset="GBK"/>
拦截hessian请求,默认以/remoting开头被该拦截器处理
根据URL的内容设置rundata相关属性
使用MappingService,将target的后缀转换成统一的内部后缀,将字符串转换成camel case
StringUtil.toCamelCase(null) = null
StringUtil.toCamelCase("") = ""
StringUtil.toCamelCase("aBc") = "aBc"
StringUtil.toCamelCase("aBc def") = "aBcDef"
StringUtil.toCamelCase("aBc def_ghi") = "aBcDefGhi"
StringUtil.toCamelCase("aBc def_ghi 123") = "aBcDefGhi123"
在webx中,需要将模板名转换成相应的module类名或layout模板名,代码如下:
getMappingService()获取DefaultMappingService,其extension.input定义如下:
<property name="mapper.extension.input">
<property name="class" value="com.alibaba.turbine.service.mapping.mapper.ExtensionMapper"/>
<!-- 默认后缀 -->
<property name="extension." value=""/>
<!-- JSP -->
<property name="extension.jhtml" value="jsp"/>
<property name="extension.jsp" value="jsp"/>
<property name="extension.php" value="jsp"/>
<!-- Velocity -->
<property name="extension.htm" value="vm"/>
<property name="extension.vhtml" value="vm"/>
<property name="extension.vm" value="vm"/>
</property>
即通过getMapper("extension.input"). getMappedName("/common/index.htm")进行后缀的转换,转换逻辑在ExtensionMapper中。
如果映射规则存在,则替换后缀htm后缀为vm,如果以/结尾,就不加后缀,设置完成后放到runData的target属性中
如果在上面未找到后缀转换信息,如.json的后缀,那么ExtensionMapper会直接返回该后缀,不进行转换
ExtensionMapper 转换后的target比较重要,后续pipeline的ChooseValve在选择Module处理的时候会根据该后缀判断由哪些value处理。
执行action screen权限检查
数据提交commitRunData
在AbstractWebxControllerServlet提交数据如下:
调用RequestContextChainServiceImpl的commitRequestContext方法:
此处的调用过程刚好跟RequestContext包装过程顺序相反,依次调用每个包装类的提交方法
Module加载及响应
在2.2节中 采用pipeline机制处理请求,其中有一个是value是ChooseValve,如下:
<valve class="com.alibaba.turbine.pipeline.ChooseValve" label="processModule">
<when extension="jsp, vm">
<valve class="com.alibaba.turbine.pipeline.PerformActionValve" actionParam="action"/>
<valve class="com.alibaba.turbine.pipeline.PerformScreenTemplateValve"/>
</when>
<when extension="json">
<valve class="com.alibaba.china.common.webx.valve.PerformJsonValve"/>
</when>
<when extension="do">
<valve class="com.alibaba.turbine.pipeline.PerformActionValve" actionParam="action"/>
<valve class="com.alibaba.turbine.pipeline.PerformScreenValve"/>
</when>
</valve>
ChooseValve中覆盖了
public TagLibrary getCustomizedTagLibrary() 方法,该方法定义在Customizable接口中,以支持jelly.TagLibrary的解析。
通过以上标签解析将对应的value设置到成员变量conditions 中,pipeline中处理的时候调用ChooseValve的invoke方法,该方法遍历conditions中,查找符合条件的condition,执行子value的调用。这里符合条件如TargetExtensionCondition,该类用来判断runData中的target后缀是否符合配置文件中的extension属性值,如果符合,则进行这些value调用。
即如果是以htm、html结尾的url, 则PerformActionValve、PerformScreenTemplateValve会被调用。
PerformActionValve 首先会判断请求参数中是否包含默认的action键,如果没有,直接返回空。否则开始跟ModuleLoaderService交互,取得对应的action module,具体实现代码如下:
以上代码调用具体由DefaultModuleLoaderService的
public Module getModuleInternal (String moduleType, String moduleNameFQ)
方法完成,该方法完成对应类型和module的加载,该方法实现代码如下:
继续看下loadModule(moduleType, moduleName)的加载过程,其通过ModuleFactory的工厂方法创建对应Module,实现如下:
ModuleFactory是通过查找Configuration中module.factory的配置信息取得的,
moduleLoaderService的配置信息如下:
其module.factory为com.alibaba.turbine.service.moduleloader.factory.
DefaultModuleFactory,且其对应的key 为default.
若其他地方未在配置module.factory,则程序中默认取DefaultModuleFactory创建Module,其通过 package + moduleType + moduleName作为类名进行module的加载。
package 通过module.packages取得,而module.packages在各自模块的webx.xml配置如下:
<service name="ModuleLoaderService">
<property name="module.packages">
<value>com.sylinxsoft.webxdemo.web.module1.module</value>
</property>
</service>
因此请求一个action如Hello,则最终会加载的类为com.sylinxsoft.webxdemo.web.module1.module.action.Hello。
程序中使用遍历每个模块,直到加载到对应的类则停止尝试加载。
注 : 如果模块比较多,会有效率问题?
定位到该module后,判断该类是否是抽象的,如果是,则用CGLIB创建子类。否则直接newInstance。生成实例后,通过resolver注入相关属性,resolver可以通过module.ref.resolver.class在配置文件中指定,如果没有指定,则采用默认的ResolverWrapper,目前还有另一种选择实现是SpringReferenceResolver
实例化后调用module.init进行初始化。
每个module必须实现Module接口,其已有实现如下图:
其有个主要方法是
void execute(RunData rundata),该方法就是用来处理对应请求
注 : 虽然每个module在ModuleFactory每次都是被重新创建并调用其初始化方法的,但是由于外层如果指定cacheEnabled,则后面都是从缓存中取得。此时该module就要考虑线程安全问题。
TemplateAction
一般Action都继承TemplateAction,其父类为ActionEvent, 其execute的执行逻辑如下:
取得key=eventSubmit_do${event}, 将do${event}方法作为具体的调用目标,如果找不到该方法,则尝试使用默认的doperform。这里使用反射进行方法查找调用。
执行反射的参数获取为
Object[] methodArgs = getEventMethodParameters(rundata);
该方法在TemplateAction中覆盖为
因此最终调用module的方法为
do${event}(RunData rundata, TemplateContext context)
其中TemplateContext是从pullService中取得,关于TemplateContext的详细获取过程,在后续章节中说明
从TemplateAction 派生的对应module类的do${event}(RunData rundata, TemplateContext context)方法最终会响应请求,在这个方法里面通常会使用命令模式和其他容器交互(如spring容器、Ejb容器),交互后返回Result,将Result相关结果设置到TemplateContext中,
NoTemplateModule
纯数据输出的Module模型(不需要模板), 该类Module主要为无模板页面显示内容,一般用于ajax内容输出
查看NoTemplateModule抽象类方法,其execute(RunData rundata)方法如下:
刚开始看到该方法比较奇怪,后来回头查看pipeline.xml的配置才明白,原来NoTemplateModule的请求需要以json结尾,pipeline.xml的部分配置如下:
由上配置可以看出json结尾是由PerformJsonValve进行处理
注意json后缀是经过AnalyzeURLValve的MappingService处理过的,如果在MappingService里面未配置,则前台页面配置的url就是以json结尾。
下面具体看下PerformJsonValve处理逻辑
PerformJsonValve继承自AbstractNoTemplateVavle,在AbstractNoTemplateVavle中,其invoke方法如下:
performNoTemplateOutput(RunData rundata) 处理逻辑如下:
由此NoTemplateModule具体调用的是
execute(RunData rundata, NoTemplateContext context)
方法,其直接由具体的Module实现。
TemplateModule
TemplateModule 有模板内容输出,继承TemplateModule的有TemplateControl、TemplateLayout、TemplateScreen,TemplateModule主要是作为view来显示内容的。
其执行过程如下:
TemplateModule执行过程大概分为两个过程:
- 执行具体TemplateModul的execute方法:
具体的execute(RunData rundata, TemplateContext context)由子类提供,该方法通常会采用命令模式与AO层交互,将AO层返回的Result相关值设置到TemplateContext,该TemplateContext会在后续被具体的模板解析引擎使用。
mergeTemplate(rundata, template, context)主要跟模板服务TemplateService交互,渲染对应的模板
这里获取的template名称是在PerformScreenTemplateValve中被设置成和target一样的值.另外如果模板名称没有后缀名,则程序中会取配置文件中的
<property name="default.extension" value="vm" />
作为其后缀名去查找模板。
TemplateService会委托TemplateEngineService完成具体模板的加载与渲染,TemplateEngineService在程序中查找已经注册的模板引擎,每个模板引擎初始化的时候会获取TemplateService,然后将自身注册到TemplateService的模板管理器重,注册的时候会取配置文件中的template.extension值作为key进行自身映射,如果没有,则取TEMPLATE_EXTENSIONS变量的值作为默认,在VelocityService中其默认值为vm.典型的Velocity模板解析引擎配置如下:
<service name="VelocityService"
class="com.alibaba.service.velocity.DefaultVelocityService"
earlyInit="true">
<property name="input.encoding" value="GBK" />
<property name="parser.pool.size" value="100" />
<property name="velocimacro.library" value="macros.vm"/>
<property name="eventCartridge.classes"
value="com.alibaba.china.fasttext.security.velocity.SecurityReferenceInsertionEventHandler" />
</service>
在配置文件中可以配置多种模板解析引擎,如ftl等。
模板引擎负责查找、解析渲染模板(具体的模板引擎查找解析过程不尽相同,VelocityService等详细处理过程见后面章节),模板被解析渲染后会将结果写入到rundata.getResponse().getWriter()中,在commitRunData阶段才将数据写到response输出流中。
与Spring容器交互
通常screen、action、control是在Webx中管理, AO、BO、DO、DAO及其它service是在spring容器中管理,web层和业务逻辑层传递VO、DO(对web层污染)数据。
中间消息传递(调用)使用命令模式,可具体参考" 业务层框架Command Pattern指南.doc"
以下是具体实现:
在CommandDispatcherSelector中和spring容器交互
CommandDispatcher具体子类是CommandDispatcherLogic,其execute如下:
以事件的方式调用ao的方法