webx3对请求的处理流程详解一
来源:互联网 发布:军人网络保密自查自纠 编辑:程序博客网 时间:2024/06/05 14:18
最近写了一个小的异步框架,顺便认真研究了下一个请求处理的内部流程,所以这篇文章是一个衍生品。我们的分析从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
0 0
- webx3对请求的处理流程详解一
- squid对请求的处理流程
- Java Struts2 的请求处理流程详解
- Java Struts2 的请求处理流程详解
- Java Struts2 的请求处理流程详解
- Java Struts2 的请求处理流程详解
- struts2请求处理流程详解
- asp.net对一个请求的处理流程
- struts1处理请求的流程
- SpringMVC处理请求的流程
- SpringMVC处理请求的流程
- DispatcherServlet的请求处理流程
- tomct处理请求的流程
- SpringMVC处理请求的流程
- SpringMVC-请求的处理流程
- springMVC处理请求的流程
- django源码解析一(请求处理流程)
- Spring mvc请求处理流程详解(一)之视图解析
- iOS堆与栈的区别
- HBase分布式集群搭建
- C++创建二叉树
- //判断数组类型不包含indexOf方法
- BZOJ 2502 清理雪道 有上下界的网络流
- webx3对请求的处理流程详解一
- Java ConcurrentHashMap 源码分析(2)
- 课程Project总结 - 移动web大作业(不看微博)
- CQUPT登录页
- Linux 中mysql常用命令
- Matlab 中输入希腊字母
- 算法训练 安慰奶牛(最小生成树)
- Unity Shaders and Effects Cookbook (1-2)创建自定义漫反射光照模型
- Android组件之间 数据传递方法