解讀ember的應用模型

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之中;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章