AngularJS源码解析3:RootScope的创建过程
RootScopeProvider简介
RootScopeProvider是angularjs里面比较活跃的一个provider。它主要用来生成实例rootScope,它代表angularjs应用的根作用域。我们可以把它看成MVVM模式中的VM。
源代码如下:
- function $RootScopeProvider(){
- var TTL = 10;
- var $rootScopeMinErr = minErr('$rootScope');
- var lastDirtyWatch = null;
- this.digestTtl = function(value) {
- if (arguments.length) {
- TTL = value;
- }
- return TTL;
- };
- this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
- function( $injector, $exceptionHandler, $parse, $browser) {
- .......
- }];
- }
在$RootScopeProvider构造函数中,有一个$get属性,它的值是一个数组,数组的最后的一项是一个函数function( $injector, $exceptionHandler, $parse, $browser) ,所有的代码都在这个方法中。angularJS内置的provider都有一个$get属性,此属性值主要是定义实例化provider对象的函数体,实例化时,会通过instanceinjector.invoke来调用。在$get属性上面还有一个digestTtl属性,这个属性是用来修改angularJS默认的dirty check次数的,angularJS默认是10次。接下来,我们来看下$get属性值中的实例化
$RootScopeProvider的函数体。
- function Scope() {
- this.$id = nextUid();
- this.$$phase = this.$parent = this.$$watchers =
- this.$$nextSibling = this.$$prevSibling =
- this.$$childHead = this.$$childTail = null;
- this['this'] = this.$root = this;
- this.$$destroyed = false;
- this.$$asyncQueue = [];
- this.$$postDigestQueue = [];
- this.$$listeners = {};
- this.$$listenerCount = {};
- this.$$isolateBindings = {};
- }
- Scope.prototype = {
- constructor: Scope,
- $new: function(isolate) {
- .....
- },
- $watch: function(watchExp, listener, objectEquality) {
- ......
- },
- $watchCollection: function(obj, listener) {
- var self = this;
- var oldValue;
- var newValue;
- var changeDetected = 0;
- var objGetter = $parse(obj);
- var internalArray = [];
- var internalObject = {};
- var oldLength = 0;
- function $watchCollectionWatch() {
- newValue = objGetter(self);
- var newLength, key;
- if (!isObject(newValue)) {
- if (oldValue !== newValue) {
- oldValue = newValue;
- changeDetected++;
- }
- } else if (isArrayLike(newValue)) {
- if (oldValue !== internalArray) {
- oldValue = internalArray;
- oldLength = oldValue.length = 0;
- changeDetected++;
- }
- newLength = newValue.length;
- if (oldLength !== newLength) {
- changeDetected++;
- oldValue.length = oldLength = newLength;
- }
- for (var i = 0; i < newLength; i++) {
- if (oldValue[i] !== newValue[i]) {
- changeDetected++;
- oldValue[i] = newValue[i];
- }
- }
- } else {
- if (oldValue !== internalObject) {
- oldValue = internalObject = {};
- oldLength = 0;
- changeDetected++;
- }
- newLength = 0;
- for (key in newValue) {
- if (newValue.hasOwnProperty(key)) {
- newLength++;
- if (oldValue.hasOwnProperty(key)) {
- if (oldValue[key] !== newValue[key]) {
- changeDetected++;
- oldValue[key] = newValue[key];
- }
- } else {
- oldLength++;
- oldValue[key] = newValue[key];
- changeDetected++;
- }
- }
- }
- if (oldLength > newLength) {
- changeDetected++;
- for(key in oldValue) {
- if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) {
- oldLength--;
- delete oldValue[key];
- }
- }
- }
- }
- return changeDetected;
- }
- function $watchCollectionAction() {
- listener(newValue, oldValue, self);
- }
- return this.$watch($watchCollectionWatch, $watchCollectionAction);
- },
- $digest: function() {
- ........
- },
- $destroy: function() {
- ......
- },
- $eval: function(expr, locals) {
- .....
- },
- $evalAsync: function(expr) {
- .......
- },
- $$postDigest : function(fn) {
- ........
- },
- $apply: function(expr) {
- ......
- },
- $on: function(name, listener) {
- ......
- },
- $emit: function(name, args) {
- .......
- },
- $broadcast: function(name, args) {
- ......
- }
- };
- var $rootScope = new Scope();
- return $rootScope;
- function beginPhase(phase) {
- if ($rootScope.$$phase) {
- throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase);
- }
- $rootScope.$$phase = phase;
- }
- function clearPhase() {
- $rootScope.$$phase = null;
- }
- function compileToFn(exp, name) {
- var fn = $parse(exp);
- assertArgFn(fn, name);
- return fn;
- }
- function decrementListenerCount(current, count, name) {
- do {
- current.$$listenerCount[name] -= count;
- if (current.$$listenerCount[name] === 0) {
- delete current.$$listenerCount[name];
- }
- } while ((current = current.$parent));
- }
- function initWatchVal() {}
从代码中,可以看到。第一步,定义了一个构造函数function Scope(){},第二步,定义了Scope的原型Scope.prototype = {},第三步,new出来了一个Scope,赋值给了$rootScope,并return这个Scope实例对象。
通过第三步大家可以看出,angularJS默认会创建根作用域$rootScope,并作为$rootScopeprovider的实例对象返回。
第一步,初始化了Scope对象的一些属性值。
第二步,定义了一些方法。我们来详细讲解这些方法的作用:
- $new: function(isolate) {
- var ChildScope,
- child;
- if (isolate) {
- child = new Scope();
- child.$root = this.$root;
- // ensure that there is just one async queue per $rootScope and its children
- child.$$asyncQueue = this.$$asyncQueue;
- child.$$postDigestQueue = this.$$postDigestQueue;
- } else {
- ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges
- // the name it does not become random set of chars. This will then show up as class
- // name in the web inspector.
- ChildScope.prototype = this;
- child = new ChildScope();
- child.$id = nextUid();
- }
- child['this'] = child;
- child.$$listeners = {};
- child.$$listenerCount = {};
- child.$parent = this;
- child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
- child.$$prevSibling = this.$$childTail;
- if (this.$$childHead) {
- this.$$childTail.$$nextSibling = child;
- this.$$childTail = child;
- } else {
- this.$$childHead = this.$$childTail = child;
- }
- return child;
- },
此方法的作用是用来创建子作用域。
isolate变量的值决定是执行if语句的代码还是else语句的代码,如果isolate的值是true,那么就会创建一个独立的作用域。这个在我们创建指令,并且在创建指令的回调方法中有scope属性的情况下,会出现这种情况,当然还有其他别的特殊情况下也会这样。假如是独立作用域的话,会多一个$root属性,它的值默认指向rootscope。
如果isolate的值为false,则会定义一个空的构造函数ChildScope ,并且把此构造函数的prototype指向当前scope的实例对象,然后,new一个ChildScope 对象。
然后为生成的子作用域设置$parent属性为当前作用域,并且设置子作用域的一些默认的属性值,然后设置当前作用域的一些属性值为生成的子作用域。最后,返回这个新生成的子作用域child。
接下来,我们再来说说$watch方法:
- $watch: function(watchExp, listener, objectEquality) {
- var scope = this,
- get = compileToFn(watchExp, 'watch'),
- array = scope.$$watchers,
- watcher = {
- fn: listener,
- last: initWatchVal,
- get: get,
- exp: watchExp,
- eq: !!objectEquality
- };
- lastDirtyWatch = null;
- if (!isFunction(listener)) {
- var listenFn = compileToFn(listener || noop, 'listener');
- watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
- }
- if (typeof watchExp == 'string' && get.constant) {
- var originalFn = watcher.fn;
- watcher.fn = function(newVal, oldVal, scope) {
- originalFn.call(this, newVal, oldVal, scope);
- arrayRemove(array, watcher);
- };
- }
- if (!array) {
- array = scope.$$watchers = [];
- }
- array.unshift(watcher);
- return function() {
- arrayRemove(array, watcher);
- lastDirtyWatch = null;
- };
- }
$watch方法有三个参数,第一个是监听表达式,可以是字符串也可以是方法,第二个是监听方法,第三个参数代表是否深度监听。
方法里面首先初始化一个get局部变量,初始化get的compileToFn函数其实是调用$parse实例对象来解析监听参数的,它会返回一个方法赋给get属性,这个方法会在dirty check里用到,用来获取监听表达式的值。$parse实例对象是$parseprovider实例化生成的。
接下来,初始化了一个watcher对象,此对象用来保存一些监听相关的信息:
- fn: 代表监听方法,当监听表达式有变化时,也就说值改变时会执行此方法
- last: 保存最后一次发生变化的监听表达式的值
- get: 保存一个监听表达式对应的方法,目的是用来获取表达式的值然后用来进行新旧对比,看是否有变化
- exp: 保存一个原始的监听表达式
- eq: 保存$watch方法的第三个参数,表示是否进行深度比较
然后会检查传递进来的监听方法是否为方法,如果不是,则通过parse方法解析生成一个方法listenFn ,然后通过包装这个listenFn方法生成一个方法watcher.fn
,此方法就是被称为监听方法,并且此方法体的内容就是执行刚才生成的方法listenFn,并默认传递当前作用域作为参数。
接着会检查监听表达式是否为字符串,如果是并且监听表达式的constant为true,就进入if语句,这代表监听表达式这个字符串是一个常量。那么,angular在处理这种监听的时候,执行完一次监听方法之后就会删除这个$watch。
最后,往当前作用域里的$$watchers数组中添加$watch对象,并返回一个方法。注意这里的返回值,利用JS的闭包保留了当前的watcher变量,这个返回值方法是用来删除监听用的。
接下来,我们来说说$digest方法
digest方法是dirty check的核心,它里面的代码是先执行$$asyncQueue队列中保存的表达式,然后开启一个traverseScopesLoop循环,来循环遍历$$watchers,如果watch与上一次的值不相同,也就是被改变了,就执行watch里的监听方法。假如ttl超过了angular默认设置的值,则dirth check结束。最后执行$$postDigestQueue队列里的表达式。
- $digest: function() {
- var watch, value, last,
- watchers,
- asyncQueue = this.$$asyncQueue,
- postDigestQueue = this.$$postDigestQueue,
- length,
- dirty, ttl = TTL,
- next, current, target = this,
- watchLog = [],
- logIdx, logMsg, asyncTask;
- beginPhase('$digest');
- lastDirtyWatch = null;
- do {
- dirty = false;
- current = target;
- while(asyncQueue.length) {
- try {
- asyncTask = asyncQueue.shift();
- asyncTask.scope.$eval(asyncTask.expression);
- } catch (e) {
- clearPhase();
- $exceptionHandler(e);
- }
- lastDirtyWatch = null;
- }
- traverseScopesLoop:
- do {
- if ((watchers = current.$$watchers)) {
- length = watchers.length;
- while (length--) {
- try {
- watch = watchers[length];
- if (watch) {
- if ((value = watch.get(current)) !== (last = watch.last) &&
- !(watch.eq
- ? equals(value, last)
- : (typeof value == 'number' && typeof last == 'number'
- && isNaN(value) && isNaN(last)))) {
- dirty = true;
- lastDirtyWatch = watch;
- watch.last = watch.eq ? copy(value) : value;
- watch.fn(value, ((last === initWatchVal) ? value : last), current);
- if (ttl < 5) {
- logIdx = 4 - ttl;
- if (!watchLog[logIdx]) watchLog[logIdx] = [];
- logMsg = (isFunction(watch.exp))
- ? 'fn: ' + (watch.exp.name || watch.exp.toString())
- : watch.exp;
- logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
- watchLog[logIdx].push(logMsg);
- }
- } else if (watch === lastDirtyWatch) {
- dirty = false;
- break traverseScopesLoop;
- }
- }
- } catch (e) {
- clearPhase();
- $exceptionHandler(e);
- }
- }
- }
- if (!(next = (current.$$childHead ||
- (current !== target && current.$$nextSibling)))) {
- while(current !== target && !(next = current.$$nextSibling)) {
- current = current.$parent;
- }
- }
- } while ((current = next));
- if((dirty || asyncQueue.length) && !(ttl--)) {
- clearPhase();
- throw $rootScopeMinErr('infdig',
- '{0} $digest() iterations reached. Aborting!\n' +
- 'Watchers fired in the last 5 iterations: {1}',
- TTL, toJson(watchLog));
- }
- } while (dirty || asyncQueue.length);
- clearPhase();
- while(postDigestQueue.length) {
- try {
- postDigestQueue.shift()();
- } catch (e) {
- $exceptionHandler(e);
- }
- }
- },
通过上面的代码,可以看出,里面的核心就是两个循环,外循环保证所有的model都能检测到(do{.....} while (dirty || asyncQueue.length);),内循环则是真正的检测每个watch(traverseScopesLoop: do{......}while((current = next));)。
watch.get方法的作用是计算监听表达式的值,然后用当前的值去跟旧值进行对比,假如不相等,也就是改变了,就执行监听方法。
注意这里的watch.eq,它的作用是是否进行深度检查,equals方法是angular里的方法,用来深度对比两个对象。这里的不相等有一个例外,那就是NaN === NaN,这个永远都是false,所以这里加了判断。
比较完之后,把新值传给watch.last,然后执行watch.fn,也就是监听方法,传递三个参数给它,分别是:最新计算的值,上次计算的值(如果是第一次,则传递新值),最后一个参数是当前作用域的实例对象。
内循环里面有一个设置外循环的条件值,那就是dirty = true,也就是说只要内循环执行了一次watch,则外循环还要继续执行,这是为了保证所有的model都能监测一次,虽然这个有点浪费性能,不过超过ttl设置的值,dirty check就会强制关闭,并抛出异常。
这里的watchLog日志对象是在内循环里,当ttl低于5的时候开始记录的。
当检查完一个作用域内的所有watch之后,则开始深度遍历当前作用域的子级作用域或者父级作用域,虽然这有些影响性能。它就是不断的查找当前作用域的子级,没有子级,则开始查找兄弟节点,最后查找它的父级,是一个深度遍历查找。只要next有值,则内循环一直执行。
不过内循环也有跳出的情况,那就是当前的watch跟最后一次检查的watch相等时,就退出内循环。
注意:这个内循环是一个label(标签)语句,这个可以在循环中执行跳出操作,就像上面的break
traverseScopesLoop。
正常执行完两个循环之后,清除当前的阶段标识clearPhase();,然后开始执行postDigestQueue队列里的表达式。
接下来我们来说下$destroy方法
- $destroy: function() {
- if (this.$$destroyed) return;
- var parent = this.$parent;
- this.$broadcast('$destroy');
- this.$$destroyed = true;
- if (this === $rootScope) return;
- forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
- if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
- if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
- if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
- if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
- this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
- this.$$childTail = null;
- },
这个方法是用来销毁当前作用域的,原理主要是清空当前作用域内的一些属性,以免执行digest,$emit,$broadcast时会关联到。
里面的代码比较简单,先是通过foreach语句来遍历$$listenerCount属性,清空$$listeners,$$watchers,$$asyncQueue,$$postDigestQueue里面的值,然后再设置$parent,$$nextSibling,$$prevSibling,$$childHead,$$childTail为null。
接下来我们来说下$eval方法
此方法可以直接在angular程序里执行一个字符串表达式:
- $eval: function(expr, locals) {
- return $parse(expr)(this, locals);
- },
它的源码其实就是通过parse方法把你传进去的字符串表达式解析成一个执行表达式的方法,然后传递当前作用域以及额外的参数执行此方法。
接下来我们来说下$evalAsync方法
evalAsync方法的作用就是延迟执行表达式,并且执行完成后,不管是否异常,都触发dirty check。
- $evalAsync: function(expr) {
- if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
- $browser.defer(function() {
- if ($rootScope.$$asyncQueue.length) {
- $rootScope.$digest();
- }
- });
- }
- this.$$asyncQueue.push({scope: this, expression: expr});
- },
从上面的源码中可以看到,当前作用域内部有一个$$asyncQueue异步队列,它保存着所有需要延迟执行的表达式,此处的表达式可以是字符串也可以是方法,因为这个表达式最终会调用$eval方法。注意这里调用了$browser服务的defer方法,此方法是调用setTimeout来实现的,它的作用是延迟执行方法。
接下来我们来说下$postDigest方法
- $$postDigest : function(fn) {
- this.$$postDigestQueue.push(fn);
- },
这个方法跟evalAsync不同的是,它不会主动触发digest方法,只是往postDigestQueue队列中添加执行表达式。它会在digest方法体内最后被执行,相当于在触发dirty check之后,我们可以执行别的一些逻辑。
接下来,我们来说下$apply方法
- $apply: function(expr) {
- try {
- beginPhase('$apply');
- return this.$eval(expr);
- } catch (e) {
- $exceptionHandler(e);
- } finally {
- clearPhase();
- try {
- $rootScope.$digest();
- } catch (e) {
- $exceptionHandler(e);
- throw e;
- }
- }
- },
如果不在angularjs的上下文中执行js,但是想操作angular中的值,则必须使用此方法了。比如:在原生的DOM事件中执行想改变angular中某些model的值,这个时候就要使用$apply方法了。
代码中,首先让当前阶段标识变为$apply,然后执行$eval方法, 这个方法上面有讲到,最后执行$digest方法,使angular中的model改变。
接下来,我们来说说scope中的event模块,它提供$on,$emit,$broadcast三个方法:
$on方法
- $on: function(name, listener) {
- var namedListeners = this.$$listeners[name];
- if (!namedListeners) {
- this.$$listeners[name] = namedListeners = [];
- }
- namedListeners.push(listener);
- var current = this;
- do {
- if (!current.$$listenerCount[name]) {
- current.$$listenerCount[name] = 0;
- }
- current.$$listenerCount[name]++;
- } while ((current = current.$parent));
- var self = this;
- return function() {
- namedListeners[indexOf(namedListeners, listener)] = null;
- decrementListenerCount(self, 1, name);
- };
- },
这个方法是用来绑定事件的,里面用到了两个实例变量$$listeners, $$listenerCount,分别用来保存事件,以及事件数量计数。
分析上面的代码,可以看出每当绑定一个事件的时候,都会向$$listeners对象中添加name的属性,属性值就是事件回调函数。注意这里有个事件计数器,只要有父级,则也给父级的$$listenerCount添加name的属性,并且把name的值+1。最后这个方法返回一个取消事件的方法,在方法中会先设置$$listeners中name的属性值为null,然后调用decrementListenerCount使该事件的计数器-1。
$emit方法
- $emit: function(name, args) {
- var empty = [],
- namedListeners,
- scope = this,
- stopPropagation = false,
- event = {
- name: name,
- targetScope: scope,
- stopPropagation: function() {stopPropagation = true;},
- preventDefault: function() {
- event.defaultPrevented = true;
- },
- defaultPrevented: false
- },
- listenerArgs = concat([event], arguments, 1),
- i, length;
- do {
- namedListeners = scope.$$listeners[name] || empty;
- event.currentScope = scope;
- for (i=0, length=namedListeners.length; i<length; i++) {
- if (!namedListeners[i]) {
- namedListeners.splice(i, 1);
- i--;
- length--;
- continue;
- }
- try {
- namedListeners[i].apply(null, listenerArgs);
- } catch (e) {
- $exceptionHandler(e);
- }
- }
- if (stopPropagation) return event;
- scope = scope.$parent;
- } while (scope);
- return event;
- },
这个方法是用来触发$on绑定的事件的。原理就是循环$$listeners,检查它里面是否有值,有的话,则执行。然后依次往上检查父级,这个方法有点类似浏览器的事件冒泡机制。
上面的代码比较简单,首先定义一个事件对象的变量event,然后开启一个循环,只要scope中有值,则一直执行。这个方法的事件链是向上传递的,不过如果在事件回调函数中执行stopPropagation方法,则会停止向上传递事件。
$broadcast方法
- $broadcast: function(name, args) {
- var target = this,
- current = target,
- next = target,
- event = {
- name: name,
- targetScope: target,
- preventDefault: function() {
- event.defaultPrevented = true;
- },
- defaultPrevented: false
- },
- listenerArgs = concat([event], arguments, 1),
- listeners, i, length;
- while ((current = next)) {
- event.currentScope = current;
- listeners = current.$$listeners[name] || [];
- for (i=0, length = listeners.length; i<length; i++) {
- if (!listeners[i]) {
- listeners.splice(i, 1);
- i--;
- length--;
- continue;
- }
- try {
- listeners[i].apply(null, listenerArgs);
- } catch(e) {
- $exceptionHandler(e);
- }
- }
- if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
- (current !== target && current.$$nextSibling)))) {
- while(current !== target && !(next = current.$$nextSibling)) {
- current = current.$parent;
- }
- }
- }
- return event;
- }
这个是$emit的升级版,广播事件。它既能向上传递事件,也能向下传递事件,还能平级传递事件。核心原理就是利用深度遍历当前作用域。
代码跟$emit差不多,不同的是,这个方法是不断的取next值,而next的值则是通过深度遍历它的子级节点,兄弟节点,父级节点,并依次查找可用的名为name的事件回调函数。$broadcast方法不能常用,因为性能不是很理想。
angular是通过观察者模式,把一个model绑定到多个view中的。
如何知道model发生了变化,其实就是通过上面代码中的$watch和$digest(我们称这两个操作为脏值检测)。
如果model是深层次嵌套的结构,我们是通过对象的深比较,来知道model的某个属性是不是变化了。
上面代码中的ttl是什么意思呢?假设a和b两个方法互相watch对方,这时,a变化了,b就会变化,b变化了,a就会变化,形成一个死循环,angular内部就是通过ttl(默认为10)的值来判定,如果一个对象的值发生10次检测的过程还在变化,那么angular会强制停止,并抛出一个异常。
angular在双向绑定的时候,是如何支持表达式的呢?其实就是通过angular自带的编译器$parser对表达式进行解析做到的。
加油!
AngularJS源码解析3:RootScope的创建过程的更多相关文章
- AngularJS源码解析1:angular自启动过程
angularJS加载进来后,会有一个立即执行函数调用,在源代码的最下面是angular初始化的地方.代码展示: bindJQuery(); publishExternalAPI(angular); ...
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- Flink 源码解析 —— JobManager 处理 SubmitJob 的过程
JobManager 处理 SubmitJob https://t.zsxq.com/3JQJMzZ 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1 ...
- Flink 源码解析 —— TaskManager 处理 SubmitJob 的过程
TaskManager 处理 SubmitJob 的过程 https://t.zsxq.com/eu7mQZj 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink ...
- Netty 源码解析(九): connect 过程和 bind 过程分析
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
- AngularJS源码解析4:Parse解析器的详解
$ParseProvider简介 此服务提供者也是angularjs中用的比较多的,下面我们来详细的说下这个provider. function $ParseProvider() { var cach ...
- QT源码解析(七)Qt创建窗体的过程,作者“ tingsking18 ”(真正的创建QPushButton是在show()方法中,show()方法又调用了setVisible方法)
前言:分析Qt的代码也有一段时间了,以前在进行QT源码解析的时候总是使用ue,一个函数名在QTDIR/src目录下反复的查找,然后分析函数之间的调用关系,效率实在是太低了,最近总结出一个更简便的方法, ...
- spring源码系列8:AOP源码解析之代理的创建
回顾 首先回顾: JDK动态代理与CGLIB动态代理 Spring中的InstantiationAwareBeanPostProcessor和BeanPostProcessor的区别 我们得知 JDK ...
- springIOC源码解析之Bean的创建
上一篇讲到了beanFactory的配置文件的解析和beanFactory的创建,都集中到了obtainFreshBeanFactory();这一句代码里了,本篇主要讲bean的创建过程 public ...
随机推荐
- ansible 使用
批量添加ssh免密 ansible mhc -m authorized_key -a "user=root key='{{ lookup('file','/root/.ssh/id_dsa. ...
- Android APK反编译详解(转)
转自:http://blog.csdn.net/ithomer/article/details/6727581 这段时间在学Android应用开发,在想既然是用Java开发的应该很好反编译从而得到源代 ...
- Spring MVC的handlermapping之请求分发如何找到正确的Handler(BeanNameUrlHandlerMapping,SimpleUrlHandlerMapping)
本文讲的是Spring MVC如何找到正确的handler, 前面请求具体怎么进入到下面的方法,不再细说. 大概就是Spring mvc通过servlet拦截请求,实现doService方法,然后进入 ...
- Devexpress VCL Build v2013 vol 13.2.3 发布
继续修修补补,大过年的,就不吐槽了. What's New in 13.2.3 (VCL Product Line) New Major Features in 13.2 What's New i ...
- JScript 对象 <|> JSON
<script type="text/javascript"> function test(){ var array = [{"id":1},{&q ...
- Git config 配置文件
一.Git已经在你的系统中了,你会做一些事情来客户化你的Git环境.你只需要做这些设置一次:即使你升级了,他们也会绑定到你的环境中.你也可以在任何时刻通过运行命令来重新更改这些设置. Git有一个工具 ...
- Java之RandomAccessFile小结
今天跟大家分享一下javase中的关于I/O的操作: 有时我们需要在文件的末尾追加一些内容,在这时用RandomAccessFile就很好. 这个类有两个构造方法: RandomAccessFile( ...
- 企业搜索引擎开发之连接器connector(二十七)
ChangeQueue类实现ChangeSource接口,声明了拉取下一条Change对象的方法 * A source of {@link Change} objects. * * @since 2. ...
- 凭借对KMP算法的了解,用java实现了一下,结果和java自带的字符串indexOf比,性能差了十倍。。。
public class KMP { private char[] source = {'a','b','c','b','c','a','b','a','b','d','d','e','f','g', ...
- [leetcode] 5. Minimum Depth of Binary Tree
二叉树基本功练习题,题目如下: Given a binary tree, find its minimum depth. The minimum depth is the number of node ...