解读ember的应用模型

来源:互联网 发布:大兴区区长辞职 知乎 编辑:程序博客网 时间:2024/05/31 18:36

write by yinmingjun, 引用请注明。

序言

 

ember.js是本人看到过的最有野心的javascript的SPA框架之一,就其技术架构的设计来看,非常适合做大型SPA应用开发。不过另外一个方面,就是ember.js相关的技术文档是出奇的少,即使看ember官网提供的DEMO,也很难直观的体现出ember所具有的优势,估计很多人看过之后对ember.js还是懵懵懂懂。最近一直在读ember.js的代码,对ember.js的架构心仪不已。为了能够让更多的人了解ember,本人将基于代码分析,写一些介绍ember的技术文章,希望能对ember的推广做一点点事情。

 

ember.js在javascript框架中比较另类,其实更像是一个服务器端的框架。它在基础的继承体系和观察者模式上做了很多的基础工作,这个在我前面写的文章中能够看到。

 

接下来我们聚焦于ember.js的应用体系,了解ember.js应用框架的工作的过程。我们将从Ember.Application开始,分析Ember的应用模型的生命周期,解读ember的应用从route到页面render的过程,了解ember.js的容器概念,掌握ember.js的名称映射的处理规则。

 

ember应用的生命周期

 

1、Ember.Application的初始化过程

先看一下Ember.Application的初始化过程。

 

Ember.Application的创建代码:

App = Ember.Application.create();

 

时序图:

在调用Ember.Application的create之后,会触发一系列代码的执行,我们用时序图将执行的代码描述如下:

Application.create()

        ---->this.init() //create的初始化代码的入口

                ---->this.__container__ = this.buildContainer();   //初始化container

                ----> this.Router = this.Router || this.defaultRouter();  //设置this.Router

                ----> this.scheduleInitialize() //设置this._initialize在$.ready的时候执行

App.Router.map(function() {  //填充路由表

        //...

});

$.ready()

        ---->this._initialize()

               ----> this.register('router:main', this.Router);

               ---->Ember.runLoadHooks('application', this);

               ----> this.advanceReadiness();

                      ----> Ember.run.once(this, this.didBecomeReady)

Ember.Run()

        ---->didBecomeReady

               ----> this.setupEventDispatcher()

               ----> this.ready();

               ----> this.startRouting()

               ----> this.resolve(this);

 

描述:

Application的创建过程比较特殊,先是通过Ember.Object的create方法创建对象的实例,然后运行其提供的init方法开始其对象实例的初始化过程,在init方法中,会通过buildContainer方法为Application创建容器,容器的概念后面我们会介绍。

 

注:

看到container这个名字,我会想起来java的spring框架,两者的概念接近,都是对象的容器依赖注入的工具

 

init方法中,因用户提供的设置Route的代码还没执行,所以又提供了延迟初始化的代码,包装在其_initialize方法中,在jquery的ready方法中执行。到_initialize方法中的时候,用户的Route信息已经填充好了,接下来Application会向其container中注册'router:main'的名称,value设置为this.Router,作为router的根节点的总入口;然后运行名称为'application'load队列,最后会调度this.didBecomeReady的运行。

 

在didBecomeReady方法中,会做DOM级别的初始化,然后通过this.startRouting开始做启动默认的路由,开始页面的render。

 

2、Application的startRouting过程

 

时序图:

Application.startRouting()

        ----> var router = this.__container__.lookup('router:main');

        ----> router.startRouting();  //router是Ember.Router类型

                ----> this.handleURL(location.getURL());  //是#后面的部分,作为route的相对url

                       ----> this.router.handleURL(url) //Ember.Router的router成员的类型是Router,在router module中定义

                               ----> var results = this.recognizer.recognize(url);   //Router类的recognizer成员是RouteRecognizer类型

                                                                                                                   //定义在route-recognizer module之中

                               ---->collectObjects(this, results, 0, []);

                                       ---->setupContexts(router, objects);              //设置router的运行上下文环境

                                               ----> eachHandler(partition.entered, function(handler, context, handlerInfo) {... //对每个handler执行函数
                                                       ----> handler.setup(context)            //每个handler都是Ember.Route的实例

                                                                ----> var controller = this.controllerFor(this.routeName, context);  //如果需要,会创建controller

                                                                ----> this.controller= controller;    //设置route的controller
                                                                ----> this.setupController(controller, context);    //初始化controller

                                                                ----> this.renderTemplate(controller, context);

                                                                        ----> this.render();
                                                                               ----> view =setupView(view, container, options);
                                                                               ----> appendView(this, view, options);

                                               ----> router.didTransition(handlerInfos);    //执行route的后事件

描述:

Application的startRouting方法中,会查找其container中的名字是"router:main"来做最初的route,route的处理过程分两个阶段,第一个阶段是url的识别;第二个阶段是请求的派发。

 

url的识别过程是通过RouteRecognizer来完成的,Router其将获取的url规范化之后,通过RouteRecognizer将规范化后的url转换成解析后的结果,这个结果中的主要成分是route的name和route的handler。接下来,在请求派发的过程中,对每个handler调用其setup方法, 而在route的handler的setup的过程中,会创建和设置每个route的controller,并最终通过route的render方法,将关联的view输出到DOM树。

 

ember.js中的核心问题领域,就是根据其名称映射的规则+用户提供的route-map信息,构造出各个route的handler,并在根据规则创建controller的实例。

 

OK,我们接下来看ember.js提供的逻辑扩展点。ember.js提供了对Route的扩展;对Controller的扩展;对Template的名称映射;对View的映射规则。这些内容才是ember.js框架的核心问题领域。

 

解读ember的名称映射规则

1、了解ember的container概念

 

在前面Application的初始化的过程中,提到Application中会使用buildContainer获取一个container,这个container与spring中的container的概念是一脉相承的(与一般意义上的容器的概念大相径庭),其本意是对象的容器。在ember.js体系中,container的影响可谓巨大,渗透于ember.js对象创建的各个环节,是ember.js实现其名称映射规则的核心组件。

 

container定义于'container'模块,其包含几个方法,其中最主要的就是register、lookup和injection几个方法,分别对应与对象实例的注册和对象实例的查找和依赖的注入,涵盖contrainer的大部分的功能。我们以解析这几个方法为主线,研究container的工作过程。

 

我们先看看container中注册的name的结构,一般在container中注册的名字有下面的结构:

       typeName:partialName

 

前面的部分是typeName,是用于区分名称的不同来源,如'route','controller','template'等,后面是类型下的名称,如'Index','post'等,大多来自url的解析过程。

 

在Ember.Application类的buildContainer方法里面,就注册了大量的名称,我们看看:

buildContainer:function(namespace) {
    var container = new Ember.Container();

    Ember.Container.defaultContainer = new DeprecatedContainer(container);

    container.set=Ember.set;
    container.normalize=normalize;
    container.resolver=resolverFor(namespace);
    container.optionsForType('view', { singleton: false });
    container.optionsForType('template', { instantiate: false });
    container.register('application:main', namespace, { instantiate: false });

    container.register('controller:basic',Ember.Controller, { instantiate: false });
    container.register('controller:object',Ember.ObjectController, { instantiate: false });
    container.register('controller:array',Ember.ArrayController, { instantiate: false });
    container.register('route:basic',Ember.Route, { instantiate: false });
    container.register('event_dispatcher:main', Ember.EventDispatcher);

    container.injection('router:main','namespace','application:main');

    container.injection('controller','target','router:main');
    container.injection('controller','namespace','application:main');

    container.injection('route','router','router:main');

    return container;
  }

 

注释:

optionsForType是为特定的类型设置的构造的选项的API。injection用于提供对象实例的依赖注入,将指定的名称(类型)的构造的实例中,将指定的对象注入该实例的指定的属性之中。register可以指定自己的options,该options的使用的优先级明显是高于optionsForType指定的options。

 

接下来,我们看看Container的一些关键的实现。

Container的register方法:

     register:function(type, name, factory, options) {

        var fullName;

        if (type.indexOf(':') !== -1){

          options = factory;

          factory = name;

          fullName = type;

        } else {

          Ember.deprecate('register("'+type +'", "'+ name+'") is now deprecated in-favour of register("'+type+':'+name+'");', false);

          fullName =type + ":" + name;

        }

        var normalizedName = this.normalize(fullName);

        this.registry.set(normalizedName, factory);

        this._options.set(normalizedName, options || {});

      },

 

说明:

实例的注册方法,通过type和name来注册对象的factory。如果type中包含':',说明type包含的是fullname,去掉name参数。

 

Container的lookup方法:

     lookup:function(fullName,options) {

        fullName = this.normalize(fullName);

        options = options || {};

        if (this.cache.has(fullName) && options.singleton!== false) {

          returnthis.cache.get(fullName);

        }

        var value =instantiate(this, fullName);

        if (!value) { return; }

        if (isSingleton(this, fullName) &&options.singleton!== false) {

         this.cache.set(fullName, value);

        }

        return value;

      },

 

Container的instantiate方法: 

   functioninstantiate(container, fullName) {
      var factory =factoryFor(container, fullName);

      var splitName = fullName.split(":"),
          type = splitName[0],
          value;

      if (option(container, fullName,'instantiate') ===false) {
       return factory;
      }

      if (factory) {
        var injections = [];
        injections =injections.concat(container.typeInjections.get(type) || []);
        injections =injections.concat(container.injections[fullName] || []);

        var hash =buildInjections(container, injections);
        hash.container = container;
        hash._debugContainerKey = fullName;

        value =factory.create(hash);

        return value;
      }
    }

 

说明:

通过factoryFor找到对象的factory,根据options的'instantiate'属性决定是否实例化,如果不实例化,将factory返回;如果实例化会将定义的依赖注入到对象之中。

 

Container的injection方法:

     injection:function(factoryName,property,injectionName) {

        if (this.parent) { illegalChildOperation('injection'); }

        if (factoryName.indexOf(':') === -1) {

          return this.typeInjection(factoryName, property, injectionName);

        }

        var injections = this.injections[factoryName] = this.injections[factoryName] || [];

       injections.push({ property: property, fullName:injectionName});

      },

 

说明:

如果factoryName参数中没有':',说明依赖注入的是整个大的类型;否则只是针对指定的factoryName的依赖注入。

 

Container的factoryForresolve方法: 

    function factoryFor(container, fullName) {

      var name = container.normalize(fullName);

      return container.resolve(name);

    }

 

     resolve:function(fullName) {
        returnthis.resolver(fullName) || this.registry.get(fullName);
      },

 

说明:

定位对象的factory,通过其resolver和registry来查找的,resolver的优先级更高。

 

注释: 

顺便说一下,作为组织代码的主要的场所,Application通过其registerinject两个API发布了container的部分功能,对于注册对象工厂和依赖注入提供支持。代码如下:

 

  register: function() {
    var container = this.__container__;
    container.register.apply(container, arguments);
  },

  inject: function(){
    var container = this.__container__;
    container.injection.apply(container, arguments);
  },

 

2、Container的resolver的来源

 

上面看到,在container实例化一个对象之前,需要先找到其factory,这个过程需要container的resolver的帮助。那container的resolver是怎么来的呢?

 

在Application的buildContainer方法中,有一行代码:

container.resolver=resolverFor(namespace);

 

请记住namespace就是Application。

 

接下来看resolverFor方法:

functionresolverFor(namespace) {
  varresolverClass=namespace.get('resolver')||Ember.DefaultResolver;
  var resolver = resolverClass.create({
    namespace: namespace
  });
  return function(fullName) {
    return resolver.resolve(fullName);
  };
}

也就是说,我们可以在Application的'resolver'属性中,指定我们提供的Resolver类;要么就使用Ember.DefaultResolver类。这个策略很灵活,我很喜欢。

 

3、Ember.DefaultResolver类分析

 

这个类我们先看其核心的resolve方法:

 resolve:function(fullName) {

    var parsedName = this.parseName(fullName),

       typeSpecificResolveMethod= this[parsedName.resolveMethodName];

    if (typeSpecificResolveMethod) {

      var resolved =typeSpecificResolveMethod.call(this, parsedName);

      if (resolved) { return resolved; }

    }

    return this.resolveOther(parsedName);

  },

 

说明:

这个比较简单,先将fullName交给parseName方法解析,然后在自己的方法表中找parsedName.resolveMethodName方法 ,如果存在,调用该方法获取factory,否则通过resolveOther来获取factory。

 

parseName方法:

 parseName:function(fullName) {

    var nameParts = fullName.split(":"),

        type nameParts[0]fullNameWithoutType = nameParts[1],

        name = fullNameWithoutType,

        namespace get(this, 'namespace'),

       root=namespace;

    if (type !== 'template' && name.indexOf('/') !== -1) {

      var parts = name.split('/');

      name parts[parts.length - 1];

      var namespaceName capitalize(parts.slice(0, -1).join('.'));

      root Ember.Namespace.byName(namespaceName);

      Ember.assert('You are looking for a ' + name + ' ' + type + ' in the ' + namespaceName + ' namespace, but the namespace could not be found', root);

    }

    return {

      fullName: fullName,

      type: type,

      fullNameWithoutType: fullNameWithoutType,

      name: name,

      root: root,

      resolveMethodName"resolve" + classify(type)

    };

  },

 

 

说明:

解析的root是resolver的namespace属性,其实就是Application。这里有一个特例,如果type不是'template',如果名称中包含'/',会将'/'转换成'.',并将最后的一个部分作为name,前面的作为namespace,并从Ember.Namespace中查找对应名字的namespace作为root,不过这个部分的内容不是我们关注的重点了。resolveMethodName的结果是"resolve" + classify(type),也就是说template、route对应的方法是'resolveTemplate'、'resolveRoute'。

 

各个'resolve'方法:

  resolveTemplatefunction(parsedName) {

    var templateName = parsedName.fullNameWithoutType.replace(/\./g, '/');

 

    if (Ember.TEMPLATES[templateName]) {

      return Ember.TEMPLATES[templateName];

    }

 

    templateName = decamelize(templateName);

    if (Ember.TEMPLATES[templateName]) {

      return Ember.TEMPLATES[templateName];

    }

  },

 

 useRouterNamingfunction(parsedName) {

    parsedName.name = parsedName.name.replace(/\./g, '_');

    if (parsedName.name === 'basic') {

      parsedName.name = '';

    }

  },

  resolveControllerfunction(parsedName) {

    this.useRouterNaming(parsedName);

    return this.resolveOther(parsedName);

  },

  resolveRoutefunction(parsedName) {

    this.useRouterNaming(parsedName);

    return this.resolveOther(parsedName);

  },

  resolveViewfunction(parsedName) {

    this.useRouterNaming(parsedName);

    return this.resolveOther(parsedName);

  },

  resolveOtherfunction(parsedName) {

    var className classify(parsedName.name) classify(parsedName.type),

        factory get(parsedName.root, className);

    if (factory) { return factory; }

  }

 

说明:

作为template的处理,与其他类型的不同。首先,名称都是假定以'.'分割,在后续的处理上,template将'.'替换成'/',然后在template集合中查找,如果找不到,将template名称去小骆驼化(将大小写分割之处添加'_',并将首字母大写改成小写)之后再次查找,也就是说,对于'posts.index'的routeName,对应的是'posts/index'的template name。

 

而对于其他类型的resolve,会将名称中的'.'替换成'_',并做classify处理(会将'_'去掉,并将分割的单词大骆驼化处理),并将type部分的名称classify之后添加到后面(也就是说,对于'posts.index'的controller,经处理之后会映射成'PostsIndexController',route和view类似)。最后在提供的root(看上面的分析,一般是Application)中查找对应的属性,并将其返回值作为factory。有一点需要主要,name如果是basic会被特殊处理,名称会替换成空串。

 

值得注意的是resolve仅仅是一种映射的机制,任何一种类型都可以拿来resolve。

 

例:

  'template:post' //=> Ember.TEMPLATES['post']

  'template:posts/byline' //=> Ember.TEMPLATES['posts/byline']

  'template:posts.byline' //=> Ember.TEMPLATES['posts/byline']

  'template:blogPost' //=> Ember.TEMPLATES['blogPost']

                      //   OR

                      //   Ember.TEMPLATES['blog_post']

  'controller:post' //=> App.PostController

  'controller:posts.index' //=> App.PostsIndexController

  'controller:blog/post' //=> Blog.PostController

  'controller:basic' //=> Ember.Controller

  'route:post' //=> App.PostRoute

  'route:posts.index' //=> App.PostsIndexRoute

  'route:blog/post' //=> Blog.PostRoute

  'route:basic' //=> Ember.Route

  'view:post' //=> App.PostView

  'view:posts.index' //=> App.PostsIndexView

  'view:blog/post' //=> Blog.PostView

  'view:basic' //=> Ember.View

  'foo:post' //=> App.PostFoo

 

4、route table的构造

 

一般,我们在创建应用之后,最关键的任务就是维护route table。在ember.js中,route table的维护的方式如下:

App.Router.map(function() {
    // put your routes here
    this.resource( 'index', { path: '/' } ); 
    this.resource('posts', function() {
        this.route('new');

    });

});

 

ember.js对route table的维护是借助于DSL类完成的,上面的map的callback中的this,以及resource的callback中的this,都是DSL类的实例。

 

DSL类的部分代码如下:

DSL.prototype = {
  resourcefunction(nameoptionscallback) {
    if (arguments.length === 2 && typeof options === 'function') {
      callback = options;
      options = {};
    }

    if (arguments.length === 1) {
      options = {};
    }

    if (typeof options.path !== 'string') {
      options.path "/" + name;
    }

    if (callback) {
      var dsl = new DSL(name);
      callback.call(dsl);
      this.push(options.pathnamedsl.generate());
    } else {
      this.push(options.pathname);
    }
  },

  push: function(url, name, callback) {
    var parts = name.split('.');
    if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; }

    this.matches.push([url, name, callback]);
  },

  routefunction(nameoptions) {
    Ember.assert("You must use `this.resource` to nest", typeof options !== 'function');

    options = options || {};

    if (typeof options.path !== 'string') {
      options.path "/" + name;
    }

    if (this.parent && this.parent !== 'application') {
      name = this.parent + "." + name;
    }

    this.push(options.path, name);
  },

 

说明:

DSL的resource方法默认是有3个参数,name、options和callback,其中,name是用于映射的名称,options.path对应实际的url路径,callback用于书写resource内的route map。options是可以省略的参数,只提供name和callback参数,而callback参数也可以省略,只提供name参数。如果options中的path没提供,会通过'/'+name的方式构造path,这对name是'blog/post'的route,显然不是希望的结果。

 

DSL的route方法有2个参数,name和options,options参数同样可以省略。如果options中的path没提供,会用'/'+name来构造path。需要注意的是,如果存在parent,并且parent不是'application',那么会用parent+'.'+name作为route的最终名称

 

 

5、route的handler的获取过程

 

这部分我们关注ember.js是如何找到一个url对应的handler的。看下面的代码:

function getHandlerFunction(router) {
  var seen = {}, container = router.container,
      DefaultRoute = container.resolve('route:basic');

  return function(name) {
    var routeName ='route:' + name,
        handler =container.lookup(routeName);

    if (seen[name]) { return handler; }

    seen[name] = true;

    if (!handler) {
      if (name==='loading') { return {}; }
      if (name==='failure') { return router.constructor.defaultFailureHandler; }

     container.register(routeName,DefaultRoute.extend());
      handler container.lookup(routeName);

      if (get(router, 'namespace.LOG_ACTIVE_GENERATION')) {
        Ember.Logger.info("generated -> " + routeName, { fullName: routeName });
      }
    }

    if (name === 'application') {
      // Inject default `routeTo` handler.
      handler.events = handler.events || {};
      handler.events.routeTo = handler.events.routeTo || Ember.TransitionEvent.defaultHandler;
    }

    handler.routeName name;
    return handler;
  };
}

 

说明:

这部分代码要结合前面的代码来看才会理解。这段代码用来生成Ember.Router的getHandler方法,其过程很清晰。首先,默认的Route来自Container的'route:basic',就是Ember.Route;然后,会将使用'route:'+name作为名称,尝试从container中查找route的handler,从我们上面的分析知道,这会是从Application中定位factory的过程。对于同一个名称只会查找route一次。如果没找到,会将DefaultRoute作为factory注册到container之中,会在后续的lookup实例化route,并返回。

 

6、route的url的解析

 

下面,看看一个实际route的url与route mapping中的url的匹配过程,下面的代码来自"route-recognizer"模块的parse方法:

   function parse(routenamestypes) {

      // normalize route as not starting with a "/". Recognition will

      // also normalize.

      if (route.charAt(0) === "/") { route = route.substr(1); }

 

      var segments route.split("/"), results = [];

 

      for (var i=0, l=segments.length; i<l; i++) {

        var segment = segments[i], match;

 

        if (match = segment.match(/^:([^\/]+)$/)) {

          results.push(new DynamicSegment(match[1]));

          names.push(match[1]);

          types.dynamics++;

        } else if (match = segment.match(/^\*([^\/]+)$/)) {

          results.push(new StarSegment(match[1]));

          names.push(match[1]);

          types.stars++;

        } else if(segment === "") {

          results.push(new EpsilonSegment());

        } else {

          results.push(new StaticSegment(segment));

          types.statics++;

        }

      }

 

      return results;

    }

 

parse方法用于解析一段url,并将解析的结果返回。从类型上来看,url是通过'/'分割的url的片段,每个片段可能的形式有:

  • ':xxxx'    ==> DynamicSegment

  • '*xxxx'   ==> StarSegment

  • ''             ==> EpsilonSegment

  • 其他       ==> StaticSegment

 

这几个segment类型的定义如下:

    function StaticSegment(string) { this.string = string; }
    StaticSegment.prototype = {
      eachChar: function(callback) {
        var string = this.string, char;

        for (var i=0, l=string.length; i<l; i++) {
          char = string.charAt(i);
          callback({ validChars: char });
        }
      },

      regex: function() {
        return this.string.replace(escapeRegex, '\\$1');
      },

      generate: function() {
        return this.string;
      }
    };

    function DynamicSegment(name) { this.name = name; }
    DynamicSegment.prototype = {
      eachChar: function(callback) {
        callback({ invalidChars: "/", repeat: true });
      },

      regex: function() {
        return "([^/]+)";
      },

      generate: function(params) {
        return params[this.name];
      }
    };

    function StarSegment(name) { this.name = name; }
    StarSegment.prototype = {
      eachChar: function(callback) {
        callback({ invalidChars: "", repeat: true });
      },

      regex: function() {
        return "(.+)";
      },

      generate: function(params) {
        return params[this.name];
      }
    };

    function EpsilonSegment() {}
    EpsilonSegment.prototype = {
      eachChar: function() {},
      regex: function() { return ""; },
      generate: function() { return ""; }
    };

 

这几个Segment类型包含几个方法,eachChar、regex和generate,其中regex用于构造url匹配的正则表达式,generate用于从数据产生对应的url。

 

对正则表达式的匹配过程是在"route-recognizer"模块的State类的findHandler方法中:

    function findHandler(state, path) {
      var handlers = state.handlers, regex = state.regex;
      var captures path.match(regex), currentCapture = 1;
      var result = [];

      for (var i=0, l=handlers.length; i<l; i++) {
        var handler = handlers[i], names = handler.names, params = {};

        for (var j=0, m=names.length; j<m; j++) {
          params[names[j]] = captures[currentCapture++];
        }

        result.push({ handler: handler.handler, params: params, isDynamic: !!names.length });
      }

      return result;
    }

 

也就是说,会根据各个segment的regex的匹配的参数和提供的名称,来填充params。

 

这基本上明确了url进入参数列表的几种方式:

(1) 通过':xxxx'的形式;

(2) 通过'*xxxx'的形式;

两种方式是等价的,xxxx对应参数的名称,对应url的值是会被填充到params中作为value。

 

EpsilonSegment在处理中会被忽略;StaticSegment会被确保按字面的值来匹配(会去正则化处理)。

 

 

小结

 本文从ember.js的应用模型入手,重点解读了ember.js的运行时序和其route mapping的规则,ember.js支持的这种形式化的映射规则很诱人,ember.js将很多繁琐的工作都接管过去,使开发者可以专注于业务逻辑的处理。这正是我喜欢的工作方式,希望有机会将ember.js引入到自己的作品之中。

 

下面将ember的name mapping的规则做一个小的汇总:

  • ember.js通过container支持对象工厂和依赖注入,Application通过register和inject API发布container的这部分功能;

  • container中管理的名称的格式是'type:name'的模式;

  • ember.js通过Ember.DefaultResolver来支持对象工厂的默认查找规则,默认的resolver可以通过Application的'resolver'属性替换;

  • DefaultResolver对template的处理,会将templateName中的'.'替换成'/',然后在template集合中查找,如果找不到,会将template名称去小骆驼化(将大小写分割之处添加'_',并将首字母大写改成小写)之后再次查找;

  • DefaultResolver对其他类型的resolve,会将名称中的'.'替换成'_',并做classify处理(会将'_'去掉,并将分割的单词大骆驼化处理),并将type部分的名称classify之后添加到后面(也就是说,对于'posts.index'的controller,经处理之后会映射成'PostsIndexController',route和view类似)。

  • DefaultResolver提供的root一般是Application,如果name是'blog/post'的形式,会从Blog类作为root,并从其中查找对应的名字。

  • resolve仅仅是一种映射的机制,任何一种类型都可以拿来resolve。

  • map的options中,如果对应的url没有提供,默认会以'/'+name的方式构造url;

  • route map中,如果是使用resource方法注册route,会设置其下的parent是resource的名字;

  • route map中,如果是使用route方法注册name-url的mapping,会用parent+'.'+name作为route的最终名称;

  • 可以通过':xxxx'或'*xxxx'定义参数,从url中获取对应的值,填充到params之中;

 

0 0
原创粉丝点击