webx3对请求的处理流程详解一

最近写了一个小的异步框架,顺便认真研究了下一个请求处理的内部流程,所以这篇文章是一个衍生品。我们的分析从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,它是最简单模块映射规则。
  1. "/"替换成"."
  2. 除去文件名后缀。
  3. 将最后一个单词首字母改成大写,以符合模块命名的规则。
  4. 该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.
值得注意的是:(缺失图片,原网页就看不了)
  1. adapter中封装了MethodInvoker对象(通过分析方法的参数生成的)。
  2. 在DataBindingAdapter中由于只定义了一个execute方法(对应screen module),它是以一个对象存在,而在actionEventAdapter中则以map形似存在(对应action module的多个doxx方法)。
  3. 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 ()[];
 
             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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值