当前位置:首页 > 开发 > Web前端 > 前端 > 正文

58同城开源web框架 Argo (五)

发表于: 2013-09-27   作者:biocy   来源:转载   浏览次数:
摘要: 58同城开源的轻量级web框架 https://github.com/58code/Argo   有人说,为了这么一个小框架,花费这么长时间阅读,还分成这么多篇博客,小题大做。 仁者见仁智者见智吧,大家在学习过程中都有自己的一套方式方法,适合自己的就是最高效的。框架工具无分大与小、好与坏,人家花了大量时间琢磨研究这样一个框架,并且大范围运用起来,肯定是有道理的,至于能吃得

58同城开源的轻量级web框架 https://github.com/58code/Argo

 

有人说,为了这么一个小框架,花费这么长时间阅读,还分成这么多篇博客,小题大做。

仁者见仁智者见智吧,大家在学习过程中都有自己的一套方式方法,适合自己的就是最高效的。框架工具无分大与小、好与坏,人家花了大量时间琢磨研究这样一个框架,并且大范围运用起来,肯定是有道理的,至于能吃得多透,还得结合各自的实际经验,能产生共鸣是最好,没准咱还能想到作者所想不到的。

 

吐个槽先,Guice的注解风格提高了调试成本,我都找不到在哪实例化的ArgoDispatcher,只能看Type hierarchy,哪个实现类有Guice的实现注解。麻烦死了。。。。

 

DefaultArgoDispatcher 是默认的 ArgoDispatcher 实现,有注解 @Singleton。但是看到这里也没看到进行的controller实例化,以及url与方法的映射绑定。如果不熟悉Guice就直接来看Argo的代码,坑还是挺多的。秘密在DefaultArgoDispatcher 的构造方法上面

 

@Inject
public DefaultArgoDispatcher(Argo argo, Router router, StatusCodeActionResult statusCodeActionResult, MultipartConfigElement config) {
        this.argo = argo;

        this.router = router;
        this.statusCodeActionResult = statusCodeActionResult;
        this.config = config;

        this.logger = argo.getLogger(this.getClass());

        logger.info("constructed.", this.getClass());
    }

 @Inject 是通过Guice对参数列表进行实例注射,因此参数中实例的构造方法也都会执行一遍。Router 的默认实现是DefaultRouter,构造方法中有

this.actions = buildActions(argo, controllerClasses, staticAction);

 

actions?不是controller么?参数列表中的@ArgoSystem 和 @StaticActionAnnotation又是怎么回事,又是坑啊。经过一番整理,结果如下:

@ArgoSystem 和 @StaticActionAnnotation  这两个解标注了Guice的 @BindingAnnotation  参考Guice文档,用于Argo内部注入用。com.bj58.argo.inject.ArgoModule 中可以看到两个方法

@Provides
@ArgoSystem
@Singleton
private Set<Class<? extends ArgoController>> provideControllerClasses() {
    return argo.getControllerClasses();
}

bind(Action.class).annotatedWith(StaticActionAnnotation.class)
                .to(StaticFilesAction.class);

 因此找到了@StaticActionAnnotation的对应实现类StaticFilesAction,Set<Class<? extends ArgoController>>类型的提供者,构造方法中的groupConvention.currentProject().controllerClasses();

 

于是继续向groupConvention去找,终于找到了DefaultGroupConvention,里面对框架环境的描述已经非常详细了。

     @SuppressWarnings("unchecked")
    Set<Class<? extends ArgoController>> parseControllers(GroupConventionAnnotation groupConventionAnnotation) {

        Set<Class<?>> classSet = ClassUtils.getClasses(groupConventionAnnotation.groupPackagesPrefix());

        Pattern controllerPattern = Pattern.compile(groupConventionAnnotation.controllerPattern());


        ImmutableSet.Builder<Class<? extends ArgoController>> builder = ImmutableSet.builder();

        for (Class<?> clazz : classSet)
            if (applyArgoController(clazz, controllerPattern))
                builder
                    .add((Class<? extends ArgoController>) clazz)
                    .build();

        return builder.build();
    }

 看到这个方法了吧

1. Set<Class<?>> classSet = ClassUtils.getClasses(groupConventionAnnotation.groupPackagesPrefix());  把符合约定前缀包名的Class都扫出来,默认是com.bj58.argo

2. Pattern controllerPattern = Pattern.compile(groupConventionAnnotation.controllerPattern());
读取controller的约定包名,可以是一个正则表达式,默认是.*\\.controllers\\..*Controller
3. 后面就是遍历class,校验每个class是否合法,符合controller规范。把符合规范的放到一个不可变Set中,这个过程是通过Guava来做的。

 

 

还有一个很重要的功能,就是模板变量式的路径配置(一种语法糖)。下面多贴点代码,没什么技巧、难点,应该一看就懂。比如首我定义两个KV,分别是 a=/tmp,b={a}/log,那么经过下面代码的解析,最终值就会变成 a=/tmp,b=/tmp/log

 

    private Map<String, String> parseGroupConventionPath(GroupConventionAnnotation groupConventionAnnotation
            , ProjectConvention projectConvention) {

        Map<String, String> paths = ImmutableMap.<String, String>builder()
                .put(PACKAGES_PREFIX, groupConventionAnnotation.groupPackagesPrefix())
                .put(PROJECT_ID, projectConvention.id())
                .put(GROUP_CONFIG_FOLDER, groupConventionAnnotation.groupConfigFolder())
                .put(GROUP_LOG_FOLDER, groupConventionAnnotation.groupLogFolder())
                .build();

        return matchPath(paths);

    }

    static Map<String, String> matchPath(Map<String, String> paths) {
        Map<String, String> values = Maps.newHashMap();
        Map<String, String> templates = Maps.newHashMap();
        Map<String, String> result = Maps.newHashMap();

        classify(paths, values, templates);

        while (values.size() > 0) {
            values = migrate(values, templates, result);
        }

        if (templates.size() > 0)
            throw ArgoException.newBuilder("GroupConventionAnnotation contains nested expression")
                    .addContextVariables(templates)
                    .build();

        return result;

    }

    /**
     * 将定值数据保存到结果数据集合,并将模板数据用定值数据进行替换,并返回替换后的定值
     * @param values 定值数据集合
     * @param templates 模板数据集合
     * @param result 结果数据集合
     * @return 由模板替换后生成的定值数据
     */
    static Map<String, String> migrate(Map<String, String> values, Map<String, String> templates, Map<String, String> result) {
        result.putAll(values);


        for (Map.Entry<String, String> templateItem : templates.entrySet()) {
            String v = templateItem.getValue();
            for (Map.Entry<String, String> valueItem : values.entrySet()) {
                String exp = '{' + valueItem.getKey() + '}';

                if (v.contains(exp))
                    v = v.replace(exp, valueItem.getValue());

            }

            if (!v.equals(templateItem.getValue()))
                templateItem.setValue(v);
        }

        Map<String, String> newValues = Maps.newHashMap();
        Map<String, String> newTemplates = Maps.newHashMap();
        classify(templates, newValues, newTemplates);

        templates.clear();
        templates.putAll(newTemplates);


        return newValues;

    }


    /**
     * 分类数据,若路径中存在"{",刚归类到模板数据,否则归类到定值数据
     * @param paths 路径数据集合
     * @param values 定值数据集合
     * @param templates 模板数据集合
     */
    static void classify(Map<String, String> paths, Map<String, String> values, Map<String, String> templates) {
        for (Map.Entry<String, String> entry : paths.entrySet()) {
            if (entry.getValue().contains("{"))
                templates.put(entry.getKey(), entry.getValue());
            else
                values.put(entry.getKey(), entry.getValue());

        }
    } 

 

今天的最后说的是页面模板,Argo的抽象接口是ViewFactory,唯一需要实现的方法是ActionResult create(String viewName);

 

/**
 * 提供View工厂,默认采用velocity模板
 */
@ImplementedBy(VelocityViewFactory.class)
public interface ViewFactory {
    ActionResult create(String viewName);
}

 

AbstractController 中的 view(String viewName) 方法是页面生成渲染时需要用到的,里面调用viewFactory.create(viewName);来实现。

所以如果我们使用Argo,又不想用velocity做页面模板,就需要再实现一套ViewFactory,把@ImplementedBy注解中的Class改成自己的就ok了。

com.bj58.argo.internal.VelocityViewFactory 

这个类里面是velocity的一些配置,都是常见的,也不多说明了。

 

58同城开源web框架 Argo (五)

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

编辑推荐
58同城开源的轻量级web框架 https://github.com/58code/Argo 有的时候总是在想,什么样的工具才算是
WebWork WebWork是由OpenSymphony组织开发的,致力于组件化和代码重用的拉出式MVC模式J2EE Web框架
58同城数据库架构设计思路 58同城,一个被贴上“神奇”标签的网站,海量信息背后到底支撑的数据库是
Android例子源码类似58同城的通过滑屏控制引导页 运行效果图如下,向右滑动屏幕,小人的小腿在跑呀
原文网址链接:http://www.csdn.net/article/2015-10-24/2826028 摘要:对很多创业公司而言,很难在
  特地等了好久才等到今天发布,想着好歹也算个特殊点的日子。   HoorayOS从最早只是一个想法,
Aranea Aranea是一个开源,面向组件,事件驱动的Java MVC Web框架。它提供一种通用简单的方式来构建
Rose 是由 人人网、糯米网 提供的、基于Servlet规范、Spring“规范”的开放源代码WEB开发框架。 Ros
Aurora Aurora是一个开源的,可配置的,完全面向对象的MVC框架。Aurora的form控制器用的是Spring框架
开源框架Pushlet入门 一、comet基本概念 1.comet是一个用于描述客户端和服务器之间交互的术语,即使
版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号