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,它是最简单模块映射规则。
  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
0 0
原创粉丝点击