最近写了一个小的异步框架,顺便认真研究了下一个请求处理的内部流程,所以这篇文章是一个衍生品。我们的分析从pipeline开始,前面的filter到pipeline的过程就略过了!
整体简化版流程
我们以一个screen的处理为例来说明整体的处理流程,如下图所示:
在深入每一步的细节之前,我们先了解一些背景知识。
target
官方解释:target是一个抽象的概念,指明当前请求要完成的任务。Target由pipeline来解释,它可能被解释成模板名,也可能被解释成别的东西。
module
官方解释:在Webx Turbine中,module是指screen、action、control等,大致相当于其它框架中的action或者controller。
对于webx来说它是能被框架处理的独立单元,一个请求最终会被映射到某个module进行处理。
module在webx中被定义成了接口,这意味着我们可以实现module来扩展自定义module,例如webx-rpc框架就定制了自己的
RPCModule来处理异步请求。
好了,开始步入正题!
分析URL
分析url,最主要的目的是通过url得到target,这一过程需要依赖mappingRuleService来完成。
MappingRuleService
类图(缺失,原网页就看不了):
对应的sample配置如下:
<services:mapping-rules xmlns=
“http://www.alibaba.com/schema/services/mapping-rules” >
<!– External target name => Internal target name –>
<extension-rule id =
“extension.input”>
<!– 默认后缀 –>
<mapping extension =
“” to=
“” />
<!– JSP –>
<mapping extension =
“jhtml” to=
“” />
<mapping extension =
“jsp” to=
“” />
<mapping extension =
“php” to=
“” />
<!– Velocity –>
<mapping extension =
“htm” to=
“” />
<mapping extension =
“vhtml” to=
“” />
<mapping extension =
“vm” to=
“” />
</extension-rule>
<!– Internal target name => External target name –>
<extension-rule id =
“extension.output”>
<!– 默认后缀 –>
<mapping extension =
“” to=
“htm” />
<!– JSP –>
<mapping extension =
“jhtml” to=
“jhtml” />
<mapping extension =
“jsp” to=
“jhtml” />
<mapping extension =
“php” to=
“jhtml” />
<!– Velocity –>
<mapping extension =
“htm” to=
“htm” />
<mapping extension =
“vhtml” to=
“htm” />
<mapping extension =
“vm” to=
“htm” />
</extension-rule>
<!– Target name => Action module name –>
<direct-module-rule id =
“action” />
<!– Target name => Screen module name (*.do) –>
<direct-module-rule id =
“screen.notemplate” />
<!– Target name => Screen module name (*.jsp, *.vm) –>
<fallback-module-rule id =
“screen” moduleType=
“screen” />
<!– Target name => Screen template name –>
<direct-template-rule id =
“screen.template” templatePrefix=
“screen” />
<!– Target name => Layout template name –>
<fallback-template-rule id =
“layout.template” templatePrefix=
“layout” />
<!– Target name => Control module name (setControl method) –>
<direct-module-rule id =
“control.notemplate” />
<!– Target name => Control module name (setTemplate method) –>
<fallback-module-rule id =
“control” moduleType=
“control” />
<!– Target name => Control template name –>
<direct-template-rule id =
“control.template” templatePrefix=
“control” />
</services:mapping-rules>
mappingRuleService 通过一系列的mappingRule来完成映射工作。比如,url的分析就是通过这段
<!– External target name => Internal target name –>
<extension-rule id =
“extension.input”>
<!– 默认后缀 –>
<mapping extension =
“” to=
“” />
<!– JSP –>
<mapping extension =
“jhtml” to=
“” />
<mapping extension =
“jsp” to=
“” />
<mapping extension =
“php” to=
“” />
<!– Velocity –>
<mapping extension =
“htm” to=
“” />
<mapping extension =
“vhtml” to=
“” />
<mapping extension =
“vm” to=
“” />
</extension-rule>
来完成的。后面这些rule被用到的时候我们再去分析它们的作用。
moduleLoaderService的配置与初始化
target映射到module是通过moduleLoaderService来完成的。
我们不妨先看一段moduleLoaderService的配置
<services:module-loader>
<ml-factories:class-modules>
<ml-factories:search-packages type =
“$1″ packages=
“com.tmall.tmcrm.admin.module.*” />
</ml-factories:class-modules>
<ml-adapters:adapter class=
“com.alibaba.citrus.extension.rpc.integration.RPCModuleAdapterFactory” />
</services:module-loader>
xml文件中定义了namespace
xmlns:ml-adapters=
“http://www.alibaba.com/schema/services/module-loader/adapters”
xmlns:ml-factories=
“http://www.alibaba.com/schema/services/module-loader/factories”
所以事实上adapters和factories都是module-loader的捐献(donation)
对应的parser文件分别是
文件名:services-module-loader-adapters.bean-definition-parsers
内容:
action-event-adapter=com.alibaba.citrus.service.moduleloader.impl.adapter.ActionEventAdapterFactoryDefinitionParser
data-binding-adapter=com.alibaba.citrus.service.moduleloader.impl.adapter.DataBindingAdapterFactoryDefinitionParser
文件名:services-module-loader-factories.bean-definition-parsers
内容:
class-modules=com.alibaba.citrus.service.moduleloader.impl.factory.ClassModuleFactoryDefinitionParser
script-modules=com.alibaba.citrus.service.moduleloader.impl.factory.ScriptModuleFactoryDefinitionParser
moduleFactory用来加载和生成module,而adaptor用来将module适配到对应的module类型,例如screen,action.
webx默认使用的moduleFactory是com.alibaba.citrus.service.moduleloader.impl.factory.ClassModuleFactory
它的definitionParser除了解析bean的定义之外,还完成了一件重要的初始化工作:
protected void postProcessItems
(Element element
, ParserContext parserContext
, BeanDefinitionBuilder builder
,
Map
<String
, ParsingModuleInfo
> items
, String tags
) {
// 注册所有明确定义的beans
for ( ParsingModuleInfo item
: items
.values
()) {
if ( item
.bd
!= null ) {
assertNotNull
(item
. key
, “Specificly-defined module could not be found in %s: %s”
, tags
, item
.itemName
);
item
.beanName
= SpringExtUtil
.generateBeanName
(item
.getBaseBeanName
(), parserContext
.getRegistry
());
parserContext
.getRegistry
().registerBeanDefinition
(item
.beanName
, item
.bd
);
}
}
// 设置ModuleFactory.setModules()
List
<Object
> moduleList
= createManagedList
(element
, parserContext
);
log
.debug
( “Defined {} modules with {}”
, items
. size
(), getBeanClass
(null).getSimpleName
());
for ( ParsingModuleInfo item
: items
.values
()) {
if ( item
.beanName
!= null ) {
BeanDefinitionBuilder bdb
= BeanDefinitionBuilder
. genericBeanDefinition
(ModuleInfo
.class
);
bdb
.addConstructorArgValue
(item
.key
);
bdb
.addConstructorArgValue
(item
.beanName
);
bdb
.addConstructorArgValue
(item
.itemName
); // className or script resourceName
moduleList
.add
( bdb
.getBeanDefinition
());
log
.debug
( “Defined module {} with {}”
, new Object
[] { item
.key
, item
.itemName
});
}
}
builder
.addPropertyValue
(“modules”
, moduleList
);
}
这段代码完成了两件事:
第一件事,把扫描的每个module都生成一个beanDefinition(如果解析出来的beanDefinition不为空的话),并注册到BeanDefinitionRegistry。
第二件事,把所有的module的beanDefinition放在一个数组里,并添加到BeanDefinitionBuilder中,后续再注册到BeanDefinitionRegistry。
moduleLoaderService 的definitionParser除了读取配置之外,也做了额外的一件事:
if (includeDefaultAdapters
) {
// default adapter: action event adapter
addDefaultAdapter
(adapterList
, ActionEventAdapterFactory
. class
);
// default adapter: data binding adapter
addDefaultAdapter
(adapterList
, DataBindingAdapterFactory
. class
);
}
对用的xsd默认includeDefaultAdapters 为true,所有默认就添加了两个ActionEventAdapterFactory和DataBindingAdapterFactory 这两个AdaptorFactory
映射module
moduleFactory扫描了所有的module,并生成一个数组放到beanDefinition。当一个请求过来以后,我们会根据当前请求参数的名称获取对用的moduleName。
例如screen解析moduleName的方式如下:
/**
* 根据target取得 screen模块名。子类可以修改映射规则。
*/
protected String getModuleName
(String target
) {
return mappingRuleService
. getMappedName
(SCREEN_MODULE_NO_TEMPLATE
, target
);
}
这里用到了mappingRuleService里对应的这段配置
<!– Target name => Screen module name (*.do) –>
<direct-module-rule id =
“screen.notemplate” />
对应的实现类是DirectModuleMappingRule,它是最简单模块映射规则。
- 将
"/"
替换成"."
。 - 除去文件名后缀。
- 将最后一个单词首字母改成大写,以符合模块命名的规则。
- 该rule默认不cache结果。
例如:将模板名:"about/directions/driving.vm"
映射到screen module: "about.directions.Driving"
。
action解析moduleName的方式如下:
action
= mappingRuleService
.getMappedName
(ACTION_MODULE
, action
);
对应的mappingRuleService 配置
<!– Target name => Action module name –>
<direct-module-rule id =
“action” />
它的工作方式与screen module映射方式类似,只不过参数不是来自于target而是来自于请求参数里的action参数(可配置参数名)。
查找module的时候,我们除了提供moduleName,我们还需要提供module type。module type的值取决于我们在moduleLoaderService中配置:
<ml-factories:class-modules>
<ml-factories:search-packages type =
“$1″ packages=
“com.tmall.tmcrm.admin.module.*” />
</ml-factories:class-modules>
也就说出现在package 路径module单词后面的第一个单词将作为type的值。
这当然只是一种配置方式,如果你有兴趣可以阅读对应的xsd文件,位置在 /META-INF/services/module-loader/factories/class-modules.xsd,你完全可以通过修改配置来实现type的另一个定义。
接下来就要真正开始查找module了。我们在moduleLoaderService的配置和初始化中提到,definitionParser在解析过程中做了一步工作,就是把ModuleList注册到beanDefinition中去。
ModuleFactory则是这个list的直接使用者,只不过它对数据又做了一次封装:
public void setModules
(ModuleInfo
[] modules
) {
this.modules
= createHashMap
();
if ( modules
!= null) {
for ( ModuleInfo module
: modules
) {
Map
<String
, ModuleInfo
> typedModules
= this.modules
.get
( module
.getKey
().getModuleType
());
if ( typedModules
== null) {
typedModules
= createHashMap
();
this.modules
.put
( module
.getKey
().getModuleType
(), typedModules
);
}
typedModules
.put
( module
.getKey
().getModuleName
(), module
);
}
}
}
也就是按类型存放了一下。
适配module
module的适配是一个很有背景的话题,我们知道在webx2中所有的screen,action和control等module事实上必须实现module接口,也即是:
/**
* 代表一个模块。
*
*
@author Michael Zhou
*/
public interface Module
{
/**
* 执行模块。
*/
void execute
() throws Exception
;
}
如果你的module实现了这个接口,那么事实上我们也不需要做适配了。但是这种设计的诟病在于
- 约束了代码的灵活性
- 参数的获取变得非常麻烦
webx3对此做了改进,module pojo化了,也就是说module可以是任意pojo对象。这么做的结果当然让开发者更为舒适,同时扩展性也变好了。
但是想要灵活没错,但总归还得让框架能认识它,怎么做呢?答案是适配!
适配器要做的工作简单来说归结为两点
- 判断类型,决定是否要进行适配
- 实现module接口中定义execute方法
判断类型意味着我们可以针对不同的module类型进行不同的适配,不同的适配器只关注自己的module
实现module接口除了满足框架的身份要求外,我们还可以在这个过程中添加自己的逻辑,例如:参数绑定(下一部分会进行详解)。
如类图所示适配的过程其实是根据module类型找到对应的AdaptorFactory并生成对应的adapter.
值得注意的是:(缺失图片,原网页就看不了)
- adapter中封装了MethodInvoker对象(通过分析方法的参数生成的)。
- 在DataBindingAdapter中由于只定义了一个execute方法(对应screen module),它是以一个对象存在,而在actionEventAdapter中则以map形似存在(对应action module的多个doxx方法)。
- methodInvoker中封装了方法中每个参数对应的DataResolver。
这些都为参数绑定做好了准备。
参数绑定与业务方法执行
参数绑定是通过dataResolverService来完成的,首先看一段sample配置
<!– 支持注入参数。 –>
<services:data-resolver xmlns=
“http://www.alibaba.com/schema/services/data-resolver/factories” >
<turbine-rundata-resolver />
<parameter-resolver />
<form-resolver />
<!– 自定义的data-resolver –>
<custom-resolver />
</services:data-resolver>
除了最后一个,其它的默认都是webx3默认提供的,我们来看一下它的类图结构(缺失图片,原网页就看不了)
说明: DataResolver的实现类实在太多,所以上图并没有把它的实现类画出来。、
适配工作完成后,我们得到了一个module,接下来执行它的execute方法。
以screen module为例,事实上它执行的是DataBindingAdapter的execute,由于这个类整体很简单,我们把代码都贴出来
public class DataBindingAdapter
extends AbstractDataBindingAdapter
{
private final MethodInvoker executeMethod
;
DataBindingAdapter
(Object moduleObject
, MethodInvoker executeMethod
) {
super(moduleObject
);
this.executeMethod
= executeMethod
;
}
public void execute
() throws Exception
{
executeMethod
.invoke
( moduleObject
, log
);
}
@Override
public String toString
() {
MapBuilder mb
= new MapBuilder
();
mb
.append
( “moduleClass”
, moduleObject
.getClass
().getName
());
mb
.append
( “executeMethod”
, executeMethod
);
return new ToStringBuilder
(). append
(getClass
().getSimpleName
()).append
(mb
).toString
();
}
}
真正处理业务逻辑的其实是methodInvoker的invoke方法
public void invoke
(Object moduleObject
, Logger log
) throws Exception
{
Object
[] args
= new Object
[resolvers
.length
];
for ( int i
= 0
; i
< args
.length
; i
++) {
Object value
;
try {
value
= resolvers
[i
].resolve
();
} catch (SkipModuleExecutionException e
) {
if ( skippable
) {
log
.debug
( “Module execution has been skipped. Method: {}, {}”
, fastMethod
, e
.getMessage
());
return;
}
value
= e
. getValueForNonSkippable
();
}
// 特别处理:防止对primitive类型设置 null
Class
<?> paramType
= fastMethod
.getJavaMethod
().getParameterTypes
()[i
];
if ( value
== null && paramType
.isPrimitive
()) {
value
= getPrimitiveDefaultValue
(paramType
);
}
args
[i
] = value
;
}
try {
fastMethod
.invoke
( moduleObject
, args
);
} catch (InvocationTargetException e
) {
throwExceptionOrError
(e
. getCause
());
return;
}
}
这段代码简单来说干了两件事情:
- 为方法的每个参数绑定值
- 执行业务方法
action module的逻辑稍微复杂一些。它需要查找对应doxx方法,另外如果你在action里提供了beforeExecution 或者afterExecution方法 它会在业务代码执行前后执行这两个方法。
定制自己的dataResolver
尽管webx3为我们提供了很多DataResolver,如下图:(缺失图片,原网页就看不了)
但是很多情况下,这些resolver还是不够用,我们还需要定制自己的DataResolver. 那么如何定义自己的dataResolver?
步骤一,编写自定义类实现DataResolverFactory接口。
webx3会扫描所有的DataResolverFactory寻找第一个合适的DataResolver,我们无法修改webx3现有的DataResolverFactory的实现类,所以只能自己先添加一个
步骤二,编写自己的类实现DataResolver接口。
最常用的两个方式,一种是通过annotation的方式匹配DataResolver,另一种是通过参数类型的方式匹配参数
步骤三,编写webx3 donation
1.编写{donation}.xsd,并放到META-INF/services/data-resolver/factories/ 目录下
例如:
(缺失图片,原网页就看不了)
2.在META-INF目录下新增services-data-resolver-factories.bean-definition-parsers文件,并配置对应的映射
例如:
步骤四,根据添加的donation,修改DataResolverService的配置(缺失图片,原网页就看不了)
例如红色部分:
<!– 支持注入参数。 –>
<services:data-resolver xmlns=
“http://www.alibaba.com/schema/services/data-resolver/factories” >
<turbine-rundata-resolver />
<parameter-resolver />
<form-resolver />
<!– 自定义的data-resolver –>
<custom-resolver />
</services:data-resolver>
sample code:
public class CustomResolverFactory
implements DataResolverFactory
{
@Autowired
private UicReadServiceClient uicReadServiceClient
;
@Autowired
private HttpServletRequest request
;
private final ParserRequestContext parserRequestContext
;
public CustomResolverFactory
(ParserRequestContext parserRequestContext
) {
this.parserRequestContext
= assertProxy
(parserRequestContext
);
}
public DataResolver getDataResolver
(DataResolverContext context
) {
assertNotNull
(parserRequestContext
, “no ParserRequestContext defined”
);
OwnerId ownerIdAnnotation
= context
.getAnnotation
(OwnerId
.class
);
if ( ownerIdAnnotation
!= null) {
return new OwnerIdResolver
(ownerIdAnnotation
);
}
BizId bizIdAnnotation
= context
.getAnnotation
(BizId
.class
);
if ( bizIdAnnotation
!= null) {
return new BizIdResolver
(bizIdAnnotation
);
}
return null ;
}
/**
* 类 OwnerIdResolver 的实现描述:用来解析 ownerId
*
*
@author 晓飞 2013-10-11 上午10:16:53
*/
private class OwnerIdResolver
implements DataResolver
{
private OwnerId annotation
;
public OwnerIdResolver
(OwnerId annotation
) {
this.annotation
= annotation
;
}
public Object resolve
() throws IllegalArgumentException
{
if ( request
== null) {
throw new IllegalArgumentException
(“No httpServletRequest in context”
);
}
HttpSession session
= request
.getSession
();
if ( session
== null) {
throw new IllegalArgumentException
(“No session in request”
);
}
String userId
= ( String
) session
.getAttribute
(SessionKeeper
.ATTRIBUTE_USER_ID_NUM
);
ResultDO
<BaseUserDO
> result
= uicReadServiceClient
. getBaseUserByUserId
(Long
.parseLong
(userId
));
BaseUserDO data
= null ;
if ( result
.isSuccess
()) {
data
= result
.getModule
();
} else {
throw new IllegalArgumentException
(“error reading userId from uic”
);
}
if ( data
== null) {
throw new IllegalArgumentException
(“error reading userId from uic”
);
}
//检查是否是小二
if ( data
.getUserRank
().longValue
() == 46
) {
String name
= annotation
.name
();
String value
= trimToNull
(request
.getParameter
(name
));
if ( value
== null) {
value
= annotation
.defaultValue
();
}
return Long
. parseLong
(value
);
}
//如果是子账号则获取父账号 userId
if ( MMPTools
.isSubUserLogin
()) {
long id
= MMPTools
.getUserId
();
return id
;
} else {
return Long
. parseLong
(userId
);
}
}
}
/**
* 类 BizIdResolver 的实现描述:用来解析 BizId
*
*
@author 晓飞 2013-10-11 上午10:16:53
*/
private class BizIdResolver
implements DataResolver
{
private BizId annotation
;
public BizIdResolver
(BizId annotation
) {
this.annotation
= annotation
;
}
public Object resolve
() throws IllegalArgumentException
{
if ( request
== null) {
throw new IllegalArgumentException
(“No httpServletRequest in context”
);
}
HttpSession session
= request
.getSession
();
if ( session
== null) {
throw new IllegalArgumentException
(“No session in request”
);
}
String userId
= ( String
) session
.getAttribute
(SessionKeeper
.ATTRIBUTE_USER_ID_NUM
);
ResultDO
<BaseUserDO
> result
= uicReadServiceClient
. getBaseUserByUserId
(Long
.parseLong
(userId
));
BaseUserDO data
= null ;
if ( result
.isSuccess
()) {
data
= result
.getModule
();
} else {
throw new IllegalArgumentException
(“error reading userId from uic”
);
}
if ( data
== null) {
throw new IllegalArgumentException
(“error reading userId from uic”
);
}
//检查是否是小二
if ( data
.getUserRank
().longValue
() == 46
) {
if(StringUtil
.isBlank
(annotation
.valueIfStaff
())){
return null ;
}
try{
return Long
. parseLong
(annotation
.valueIfStaff
());
}catch(NumberFormatException e
){
throw new IllegalArgumentException
(“error parse value if staff string into Long type”
);
}
}
//如果是子账号则获取父账号 userId
if ( MMPTools
.isSubUserLogin
()) {
long id
= MMPTools
.getUserId
();
return id
;
} else {
return Long
. parseLong
(userId
);
}
}
}
public static class DefinitionParser
extends
AbstractSingleBeanDefinitionParser
<CustomResolverFactory
> {
@Override
protected void doParse
(Element element
, ParserContext parserContext
,
BeanDefinitionBuilder builder
) {
addConstructorArg
(builder
, false, ParserRequestContext
. class
);
}
}
}
@Retention(RetentionPolicy.
RUNTIME)
@Target({ ElementType.
PARAMETER })
public @interface OwnerId {
/**
* 参数名字
*
@author 晓飞
* 2013 -10- 9下午4:23:17
*
@return
*/
String name();
/**
* 参数默认值
*
@author 晓飞
* 2013 -10- 9下午4:23:25
*
@return
*/
String defaultValue()
default “0″ ;
}
@Retention(RetentionPolicy.
RUNTIME)
@Target({ ElementType.
PARAMETER })
public @interface BizId {
/**
* 如果是小二时返回的值
*
*
@author 晓飞 2013- 10-12下午5:02:54
*
@return
*/
String valueIfStaff()
default “0″;
}
完成这些后你就可以在screen或者action类中使用@BizId和@OwnerId绑定参数了。
渲染模板
我们在执行完module后,会往context放入数据,这些数据会作为数据源的一部分被模板引擎使用,渲染模板并生成response返回到客户端。由于篇幅的原因,这里不再扩展,我会再单独写一篇介绍velocity引擎如何在webx3中工作并渲染模板的。
转自: http://ju.outofmemory.cn/entry/96975
转自: http://ju.outofmemory.cn/entry/96975