├── doc ├── PROXYCYCLE.md ├── Model.md ├── annotation │ ├── Hierarchy.md │ ├── DI.md │ ├── AutoLoad.md │ ├── Configure.md │ ├── fiber.md │ ├── Controller.md │ └── AOP.md ├── Annotation.md └── Proxy.md ├── lib ├── annotation │ ├── aop │ │ ├── Aspect.js │ │ ├── After.js │ │ ├── Around.js │ │ ├── Before.js │ │ ├── Throws.js │ │ ├── AfterReturning.js │ │ └── PointCut.js │ ├── service │ │ └── Service.js │ ├── repository │ │ └── Repository.js │ ├── DI │ │ ├── Component.js │ │ └── Autowired.js │ ├── fiber │ │ ├── Async.js │ │ └── AsyncWrap.js │ ├── controller │ │ ├── ExceptionHandler.js │ │ ├── ResponseBody.js │ │ ├── Controller.js │ │ └── RequestMapping.js │ ├── controllerAdvice │ │ ├── ExceptionHandler.js │ │ └── ControllerAdvice.js │ ├── load │ │ └── AutoLoad.js │ ├── configure │ │ └── Configure.js │ ├── Model.js │ └── Annotation.js ├── PROXYCYCLE.js ├── base │ ├── Map.js │ ├── Collection.js │ ├── DataStructure.js │ ├── _Iterator.js │ ├── _Map.js │ ├── _Collection.js │ ├── _SortedCol.js │ └── Class.js ├── INTERCEPTOR.js ├── proxy │ ├── Express.js │ ├── ProxyFactory.js │ └── Proxy.js ├── Logger.js ├── ApplicationContext.js ├── Utils.js └── Scanner.js ├── .gitignore ├── package.json ├── bin └── node-annotation └── README.md /doc/PROXYCYCLE.md: -------------------------------------------------------------------------------- 1 | # PROXYCYCLE 2 | 模块实例方法介入时机枚举 3 | - BEFORE 方法执行前 4 | - AFTER 方法执行后 5 | - AROUND 方法执行前后,BEFORE后,AFTER前的阶段 6 | -------------------------------------------------------------------------------- /lib/annotation/aop/Aspect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-12-09 4 | * aop aspect 5 | */ 6 | /*@AutoLoad("0")*/ 7 | module.exports = require("../Annotation").extend({}, { 8 | //annotation name 9 | name: "Aspect" 10 | }); 11 | -------------------------------------------------------------------------------- /doc/Model.md: -------------------------------------------------------------------------------- 1 | # Model 2 | 注解数据模型,存储里注解内部的参数,以及注解所在的模块,注解修饰的变量名及其参数。 3 | 4 | ## 方法 5 | - name 获取注解名称 6 | - po 获取注解内部的参数 7 | - vo 获取注解修饰的变量名称 8 | - voParam 获取注解修饰的变量参数,当该变量类型为方法时才有值 9 | - classpath 获取注解所在模块的路径 10 | - exports 获取模块[Proxy](./Proxy.md) 11 | - instance 获取模块本身 12 | -------------------------------------------------------------------------------- /lib/PROXYCYCLE.js: -------------------------------------------------------------------------------- 1 | /** 2 | * proxy life cycle 3 | * @Author: robin 4 | * @Date: 2016-01-11 16:30:44 5 | * @Email: xin.lin@qunar.com 6 | * @Last modified by: robin 7 | * @Last modified time: 2016-06-21 11:34:29 8 | */ 9 | 10 | 'use strict'; 11 | 12 | module.exports = { 13 | BEFORE: 0, 14 | AFTERRETURNING: 1, 15 | AFTER: 2, 16 | AROUND: 3, 17 | THROWS: 4 18 | } 19 | -------------------------------------------------------------------------------- /lib/base/Map.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-12 4 | * Map 5 | */ 6 | var Map = require("./_Map"); 7 | var Utils = require("../Utils"); 8 | module.exports = function(protoProps, staticProps){ 9 | function createMap(){ 10 | return Utils.copyOwnProperty(Map); 11 | } 12 | return require("./Class").extend(protoProps && createMap() || {}, staticProps && createMap() || {}); 13 | }; -------------------------------------------------------------------------------- /lib/INTERCEPTOR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: robin 3 | * @Date: 2016-06-21 10:05:04 4 | * @Email: xin.lin@qunar.com 5 | * @Last modified by: robin 6 | * @Last modified time: 2016-06-23 14:40:09 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = { 12 | MODULE: 0, //module 13 | METHOD: 1, //module method or class static method 14 | PROTOTYPE: 2 //class method in prototype 15 | }; 16 | -------------------------------------------------------------------------------- /lib/base/Collection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-10 4 | * Collection 5 | */ 6 | var Collection = require("./_Collection"); 7 | var Utils = require("../Utils"); 8 | module.exports = function(protoProps, staticProps){ 9 | function createCollection(){ 10 | return Utils.copyOwnProperty(Collection); 11 | } 12 | return require("./Class").extend(protoProps && createCollection() || {}, staticProps && createCollection() || {}); 13 | }; -------------------------------------------------------------------------------- /lib/annotation/aop/After.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-12-09 4 | * point cut of aop 5 | */ 6 | /*@AutoLoad("0")*/ 7 | var PROXYCYCLE = require('../../PROXYCYCLE'); 8 | module.exports = require("./PointCut").extend({ 9 | /** 10 | * get the advice 11 | * @return {[type]} [description] 12 | */ 13 | getAdvice: function() { 14 | return PROXYCYCLE.AFTER; 15 | } 16 | }, { 17 | //annotation name 18 | name: "After" 19 | }); 20 | -------------------------------------------------------------------------------- /doc/annotation/Hierarchy.md: -------------------------------------------------------------------------------- 1 | # 层次 2 | 被这些层次注解修饰的模块都会成为node-annotation的组件存在。参数支持也和[Component](./DI.md)一样 3 | ## Controller 控制层 4 | 该层主要处理请求映射与返回 5 | ### RequestMapping 请求路径映射 6 | 会将对应的请求打到该注解所修饰的方法。 7 | ### ResponseBody 返回对象处理 8 | response.render支持对象类型,作为接口api存在的时候很方便。 9 | 10 | ## Service 服务层 11 | 该层主要处理与业务逻辑相关的内容 12 | 13 | ## Repository 存储层 14 | 该层主要与数据库打交道 15 | 16 | > 整个Demo参见[node-annotation-example](https://www.npmjs.com/package/node-annotation-example) 中的example/Hierarchy -------------------------------------------------------------------------------- /lib/annotation/aop/Around.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-12-09 4 | * point cut of aop 5 | */ 6 | /*@AutoLoad("0")*/ 7 | var PROXYCYCLE = require('../../PROXYCYCLE'); 8 | module.exports = require("./PointCut").extend({ 9 | /** 10 | * get the advice 11 | * @return {[type]} [description] 12 | */ 13 | getAdvice: function() { 14 | return PROXYCYCLE.AROUND; 15 | } 16 | }, { 17 | //annotation name 18 | name: "Around" 19 | }); 20 | -------------------------------------------------------------------------------- /lib/annotation/aop/Before.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-12-09 4 | * point cut of aop 5 | */ 6 | /*@AutoLoad("0")*/ 7 | var PROXYCYCLE = require('../../PROXYCYCLE'); 8 | module.exports = require("./PointCut").extend({ 9 | /** 10 | * get the advice 11 | * @return {[type]} [description] 12 | */ 13 | getAdvice: function() { 14 | return PROXYCYCLE.BEFORE; 15 | } 16 | }, { 17 | //annotation name 18 | name: "Before" 19 | }); 20 | -------------------------------------------------------------------------------- /lib/annotation/aop/Throws.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-12-09 4 | * point cut of aop 5 | */ 6 | /*@AutoLoad("0")*/ 7 | var PROXYCYCLE = require('../../PROXYCYCLE'); 8 | module.exports = require("./PointCut").extend({ 9 | /** 10 | * get the advice 11 | * @return {[type]} [description] 12 | */ 13 | getAdvice: function() { 14 | return PROXYCYCLE.THROWS; 15 | } 16 | }, { 17 | //annotation name 18 | name: "Throws" 19 | }); 20 | -------------------------------------------------------------------------------- /lib/annotation/aop/AfterReturning.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-12-09 4 | * point cut of aop 5 | */ 6 | /*@AutoLoad("0")*/ 7 | var PROXYCYCLE = require('../../PROXYCYCLE'); 8 | module.exports = require("./PointCut").extend({ 9 | /** 10 | * get the advice 11 | * @return {[type]} [description] 12 | */ 13 | getAdvice: function() { 14 | return PROXYCYCLE.AFTERRETURNING; 15 | } 16 | }, { 17 | //annotation name 18 | name: "AfterReturning" 19 | }); 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # log file 2 | logs/ 3 | 4 | # kdiff3 ignore 5 | *.orig 6 | 7 | # maven ignore 8 | target/ 9 | 10 | # eclipse ignore 11 | .settings/ 12 | .project 13 | .classpath 14 | 15 | # idea ignore 16 | .idea/ 17 | *.ipr 18 | *.iml 19 | *.iws 20 | 21 | # temp ignore 22 | *.log 23 | *.cache 24 | *.diff 25 | *.patch 26 | *.tmp 27 | 28 | # system ignore 29 | .DS_Store 30 | Thumbs.db 31 | 32 | # package ignore (optional) 33 | # *.jar 34 | # *.war 35 | # *.zip 36 | # *.tar 37 | # *.tar.gz 38 | 39 | node_modules/ 40 | -------------------------------------------------------------------------------- /lib/annotation/service/Service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-22 4 | */ 5 | /*@AutoLoad*/ 6 | var ApplicationContext = require('../../ApplicationContext'); 7 | 8 | module.exports = require("../Annotation").extend({ 9 | /** 10 | * the annotation affect 11 | * @return {[type]} [description] 12 | */ 13 | execute: function() { 14 | var po = this.model.po(); 15 | po && ApplicationContext.createBean(po, this.model.classpath()); 16 | } 17 | }, { 18 | //annotation name 19 | name: "Service" 20 | }); 21 | -------------------------------------------------------------------------------- /lib/annotation/repository/Repository.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-22 4 | */ 5 | /*@AutoLoad*/ 6 | var ApplicationContext = require('../../ApplicationContext'); 7 | 8 | module.exports = require("../Annotation").extend({ 9 | /** 10 | * the annotation affect 11 | * @return {[type]} [description] 12 | */ 13 | execute: function() { 14 | var po = this.model.po(); 15 | po && ApplicationContext.createBean(po, this.model.classpath()); 16 | } 17 | }, { 18 | //annotation name 19 | name: "Repository" 20 | }); 21 | -------------------------------------------------------------------------------- /lib/annotation/DI/Component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-21 4 | * auto load the module 5 | */ 6 | 'use strict'; 7 | /*@AutoLoad("0")*/ 8 | var ApplicationContext = require('../../ApplicationContext'), 9 | Path = require("path"); 10 | 11 | module.exports = require("../Annotation").extend({ 12 | /** 13 | * the annotation affect 14 | * @return {[type]} [description] 15 | */ 16 | execute: function() { 17 | var po = this.model.po(); 18 | ApplicationContext.createBean(po, this.model.classpath()); 19 | } 20 | }, { 21 | //annotation name 22 | name: "Component" 23 | }); 24 | -------------------------------------------------------------------------------- /lib/base/DataStructure.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-10 4 | * Collection 5 | */ 6 | var Collection = require("./_Collection"); 7 | var Map = require("./_Map"); 8 | var Utils = require("../Utils"); 9 | var dataStructure = require("../Constant").dataStructure; 10 | var structures = {}; 11 | structures[dataStructure.Collection] = Collection; 12 | structures[dataStructure.Map] = Map; 13 | 14 | module.exports = function(protoType, staticType){ 15 | function create(type){ 16 | return Utils.copyOwnProperty(structures[type]); 17 | } 18 | return require("./Class").extend(create(protoType), create(staticType)); 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-annotation", 3 | "version": "0.0.5", 4 | "description": "write application by annotation", 5 | "license": "MIT", 6 | "main": "bin/node-annotation", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Robinlim/node-annotation.git" 10 | }, 11 | "keywords": [ 12 | "annotation", 13 | "spring", 14 | "aop", 15 | "DI" 16 | ], 17 | "author": { 18 | "name": "robin", 19 | "email": "robinlim9@aliyun.com" 20 | }, 21 | "contributors": [{ 22 | "name": "robin", 23 | "email": "robinlim9@aliyun.com" 24 | },{ 25 | "name": "wyw", 26 | "email": "1006629314@qq.com" 27 | }], 28 | "engines": [ 29 | "node" 30 | ], 31 | "dependencies": { 32 | "fibers": "^1.0.13", 33 | "lodash": "~3.10.1", 34 | "promise": "7.0.0", 35 | "trycatch": "^1.5.21" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /doc/annotation/DI.md: -------------------------------------------------------------------------------- 1 | # DI 2 | ## Component 3 | 被该注解修饰的模块会成为node-annotation的组件存在。可通过 **Autowired** 注解来注入,或者ApplicationContext来获取,如下: 4 | ```javascript 5 | require('node-annotation').ApplicationContext.getBean('组件名称'); 6 | ``` 7 | 支持参数 **Component("组件名称")** 8 | - 组件名称 当有此参数的时候,会以该名称命名组件,否则会以当前所在模块的文件名称来命名。 9 | 10 | ## Autowired 11 | 能够引用到node-annotation的组件。 12 | 支持参数 **Autowired(“组件名称”)** 13 | - 组件名称 支持两种格式 14 | * 别名 通过别名的方式来获取对应的组件 15 | * 相对路径 相对于当前路径的方式来获取对应的组件 16 | 17 | ## 例子 18 | > 路径 webApp/service/Service.js 19 | 20 | ```javascript 21 | @Component('service') 22 | module.exports = { 23 | ... 24 | }; 25 | ``` 26 | > 路径 webApp/controller/User.js 27 | 28 | ```javascript 29 | ...... 30 | @Autowired('service') 或者 @Autowired('../service/Service') 31 | service: null, 32 | ...... 33 | ``` 34 | 35 | > 整个Demo参见[node-annotation-example](https://www.npmjs.com/package/node-annotation-example) 中的example/DI -------------------------------------------------------------------------------- /bin/node-annotation: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nodeAnnotation = module.exports = { 4 | Annotation: require('../lib/annotation/Annotation'), 5 | ApplicationContext: require('../lib/ApplicationContext'), 6 | PROXYCYCLE: require('../lib/PROXYCYCLE'), 7 | INTERCEPTOR: require('../lib/INTERCEPTOR'), 8 | start: function(path, ignoreDirs, callback) { 9 | if (typeof ignoreDirs == 'function') { 10 | callback = ignoreDirs; 11 | ignoreDirs = null; 12 | } 13 | var Scanner = require('../lib/Scanner'); 14 | new Scanner(path, ignoreDirs).execute(callback); 15 | } 16 | } 17 | 18 | var FUNS = ['app', 'configurePath', 'setGlobalErrorHandler', 'setLogger']; 19 | FUNS.forEach(function(el){ 20 | nodeAnnotation[el] = function(){ 21 | var appctx = nodeAnnotation.ApplicationContext; 22 | appctx[el].apply(appctx, arguments); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /lib/annotation/DI/Autowired.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-21 4 | * auto load the module 5 | */ 6 | 'use strict'; 7 | /*@AutoLoad("0")*/ 8 | var Path = require('path'); 9 | 10 | var ApplicationContext = require('../../ApplicationContext'); 11 | 12 | var FILE_REG = /^\.+\//; 13 | module.exports = require("../Annotation").extend({ 14 | /** 15 | * the annotation affect 16 | * @return {[type]} [description] 17 | */ 18 | execute: function() { 19 | var model = this.model, 20 | proxy = model.exports(), 21 | classpath = model.classpath(), 22 | po = model.po(); 23 | if (FILE_REG.test(po)) { 24 | po = Path.join(Path.dirname(classpath), po); 25 | } 26 | ApplicationContext.getBean(po || model.vo()).then(function(bean) { 27 | proxy.instance()[model.vo()] = bean.instance(); 28 | }); 29 | } 30 | }, { 31 | //annotation name 32 | name: "Autowired" 33 | }); 34 | -------------------------------------------------------------------------------- /lib/annotation/fiber/Async.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Description 3 | * @Author: wyw 4 | * @Date: 2016-06-21 15:56:11 5 | */ 6 | /*@AutoLoad*/ 7 | var Future = require('fibers/future'); 8 | var Async = module.exports = require("../Annotation").extend({ 9 | /** 10 | * the annotation affect 11 | * @return {[type]} [description] 12 | */ 13 | execute: function() { 14 | var model = this.model, 15 | vo = model.vo(), 16 | instance = model.instance(), 17 | method = instance[vo].bind(instance); 18 | 19 | instance[vo] = function(){ 20 | var argu = arguments, 21 | future = method.future(); 22 | var promise = future.apply(future, argu).promise(); 23 | promise.await = function(){ 24 | return future.apply(future, argu).wait(); 25 | } 26 | return promise; 27 | } 28 | }, 29 | compile: function(model){ 30 | //model.exports() 31 | } 32 | }, { 33 | //annotation name 34 | name: "Async" 35 | }); 36 | -------------------------------------------------------------------------------- /doc/Annotation.md: -------------------------------------------------------------------------------- 1 | # 自定义注解 2 | ## 如何被扫描器识别 3 | 需要加注解 **AutoLoad** ,这样才会被识别,参见 [AutoLoad](./annotation/AutoLoad.md) 4 | 5 | ## 如何成为注解类 6 | 需要继承自`require('node-annotation').Annotation`,直接使用其.extend方法即可 7 | 8 | > `extend`方法的第一个参数为类的所有成员方法/变量的对象,第二个参数为类的所有静态方法/变量的对象 9 | 10 | ## 注解数据模型 11 | 请参见 [Model](./Model.md) 12 | 13 | ## 模块代理 14 | 请参见 [Proxy](./Proxy.md) 15 | 16 | ## 模版 17 | 18 | ```javascript 19 | 'use strict'; 20 | /*@AutoLoad*/ 21 | module.exports = 22 | require('node-annotation').Annotation.extend({ 23 | /** 24 | * business realization 25 | * @return 26 | */ 27 | execute: function() { 28 | //注解业务实现,必须在子类中(即这里)重写该方法 29 | // 可以在这里使用this.model拿到该注解模型 30 | // 可以在这里使用this.data拿到compile的返回结果 31 | // 可以在这里使用this.traverse(function(){}, this)遍历其所有子注解 32 | // 这里可以返回一个Promise,方便内部处理异步操作 33 | }, 34 | /** 35 | * compile the model 36 | * @param {[Model]} model [annotation data] 37 | * @return 38 | */ 39 | compile: function(model) { 40 | //处理注解模型参数,返回需要的数据,可以被this.data取到 41 | } 42 | }, { 43 | name: 'AnnotationName' 44 | }); 45 | ``` 46 | -------------------------------------------------------------------------------- /lib/base/_Iterator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-16 4 | * Iterator 5 | */ 6 | module.exports = Iterator; 7 | 8 | function Iterator(collection) { 9 | this._collection = collection; 10 | this._index = -1; 11 | this._len = collection.length; 12 | } 13 | Iterator.prototype = { 14 | constructor: Iterator, 15 | hasNext: function() { 16 | return this._len != 0 && this._index < this._len - 1; 17 | }, 18 | hasPre: function() { 19 | return this._index > 0 20 | }, 21 | next: function() { 22 | return this._collection[++this._index]; 23 | }, 24 | pre: function() { 25 | return this._collection[--this._index]; 26 | }, 27 | index: function(index) { 28 | if (typeof index == "undefined") { 29 | return this._index; 30 | } 31 | return this._collection[index]; 32 | }, 33 | go: function(index) { 34 | return this._index = index; 35 | }, 36 | isEnd: function() { 37 | return this._index == this._len - 1; 38 | }, 39 | isFirst: function() { 40 | return this._index == 0; 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /doc/annotation/AutoLoad.md: -------------------------------------------------------------------------------- 1 | # AutoLoad 2 | > 自动加载模块,一般有继承关系处理的类用该标签来加载,注解扩展就是通过该方式来运作的 3 | 4 | ## 参数 5 | 6 | - `order` 加载优先级,数值越小,加载顺序越靠前(此注解@Autoload会在所有其他注解加载直接生效) 7 | - type: intenger(>0) 8 | - default: infinite 9 | - notice: 如果你的模块中有使用注解,请使用大于10的数值标示顺序,注解库中的预置注解使用0~10来划分顺序 10 | 11 | ## Demo 12 | 13 | ```javascript 14 | /*@AutoLoad("0")*/ 15 | var Path = require('path'); 16 | 17 | var ApplicationContext = require('../../ApplicationContext'); 18 | 19 | var FILE_REG = /^\.+\//; 20 | module.exports = require("../Annotation").extend({ 21 | /** 22 | * the annotation affect 23 | * @return {[type]} [description] 24 | */ 25 | execute: function() { 26 | var model = this.model, 27 | proxy = model.exports(), 28 | classpath = model.classpath(), 29 | po = model.po(); 30 | if (FILE_REG.test(po)) { 31 | po = Path.join(Path.dirname(classpath), po); 32 | } 33 | ApplicationContext.getBean(po || model.vo()).then(function(bean) { 34 | proxy.instance()[model.vo()] = bean.instance(); 35 | }); 36 | } 37 | }, { 38 | //annotation name 39 | name: "Autowired" 40 | }); 41 | 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /lib/annotation/fiber/AsyncWrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Description 3 | * @Author: wyw 4 | * @Date: 2016-06-21 15:56:21 5 | */ 6 | /*@AutoLoad*/ 7 | var Future = require('fibers/future'), 8 | utils = require("../../Utils"); 9 | 10 | Future.prototype.await = Future.prototype.wait; 11 | var AsyncWrap = module.exports = require("../Annotation").extend({ 12 | /** 13 | * the annotation affect 14 | * @return {[type]} [description] 15 | */ 16 | execute: function() { 17 | var model = this.model, 18 | data = this.data, 19 | vo = model.vo(), 20 | instance = model.instance(); 21 | instance[vo] = Future.wrap(instance[vo], data.multi, data.suffix || 'Sync', data.stop); 22 | }, 23 | compile: function(model){ 24 | var po = model.po(); 25 | if(po && utils.typeofObject(po).value == "[object Object]"){ 26 | return { 27 | multi: po.multi, 28 | suffix: po.suffix, 29 | stop: po.stop 30 | } 31 | } else { 32 | return {}; 33 | } 34 | } 35 | }, { 36 | //annotation name 37 | name: "AsyncWrap" 38 | }); -------------------------------------------------------------------------------- /lib/proxy/Express.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | var INTERCEPTOR = require('../INTERCEPTOR') 6 | 7 | /** 8 | * Express it is to filer all path relative to the project root 9 | * @param {[Regular]} regular [description] 10 | * @param {[Array<{advice, method, callback}>]} watchers [description] 11 | */ 12 | module.exports = function(regular, watchers) { 13 | var _watchers = watchers; 14 | 15 | function Express() {} 16 | 17 | Express.prototype = { 18 | constructor: Express, 19 | /** 20 | * whether or not match the path 21 | * @param {[String]} path [file path relative to the project root] 22 | * @return {[Boolean]} 23 | */ 24 | match: function(path) { 25 | return regular.test(path); 26 | }, 27 | /** 28 | * monitor the module instance 29 | * @param {[Proxy]} proxy 30 | * @return {[void]} 31 | */ 32 | watch: function(proxy) { 33 | _.forEach(_watchers, function(watcher) { 34 | proxy.addInterceptor(watcher.advice, watcher.method, watcher.interceptor, watcher.callback); 35 | }); 36 | } 37 | }; 38 | return new Express; 39 | }; 40 | -------------------------------------------------------------------------------- /lib/annotation/controller/ExceptionHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Description 3 | * @Author: wyw 4 | * @Date: 2016-06-02 15:27:37 5 | */ 6 | /*@AutoLoad*/ 7 | var ExceptionHandler = module.exports = require("./Controller").extend({ 8 | /** 9 | * compile the model 10 | * @param {[Model]} model [annotation data] 11 | * @return {[type]} [description] 12 | */ 13 | compile: function(model) { 14 | var instance = model.instance(), 15 | po = model.po(); 16 | // Error type 17 | // check po intanceof Error? 18 | return { 19 | match: matchFactory(po), 20 | fun: instance[model.vo()] 21 | }; 22 | } 23 | }, { 24 | //annotation name 25 | name: "ExceptionHandler" 26 | }); 27 | 28 | 29 | function matchFactory(po){ 30 | if(!po){ 31 | return function(){ 32 | return true; 33 | } 34 | } else { 35 | var pos = po.indexOf('='); 36 | if(pos > 0){ 37 | var key = po.slice(0,pos), 38 | value = po.slice(pos+1); 39 | return function(err){ 40 | return err[key] == value; 41 | } 42 | } else { 43 | return function(err){ 44 | return err.message == po; 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /lib/annotation/controllerAdvice/ExceptionHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Description 3 | * @Author: wyw 4 | * @Date: 2016-06-02 15:27:37 5 | */ 6 | /*@AutoLoad*/ 7 | var ExceptionHandler = module.exports = require("./ControllerAdvice").extend({ 8 | /** 9 | * compile the model 10 | * @param {[Model]} model [annotation data] 11 | * @return {[type]} [description] 12 | */ 13 | compile: function(model) { 14 | var instance = model.instance(), 15 | po = model.po(); 16 | // Error type 17 | // check po intanceof Error? 18 | return { 19 | match: matchFactory(po), 20 | fun: instance[model.vo()] 21 | }; 22 | } 23 | }, { 24 | //annotation name 25 | name: "ExceptionHandler" 26 | }); 27 | 28 | 29 | function matchFactory(po){ 30 | if(!po){ 31 | return function(){ 32 | return true; 33 | } 34 | } else { 35 | var pos = po.indexOf('='); 36 | if(pos > 0){ 37 | var key = po.slice(0,pos), 38 | value = po.slice(pos+1); 39 | return function(err){ 40 | return err[key] == value; 41 | } 42 | } else { 43 | return function(err){ 44 | return err.message == po; 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /lib/Logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Logger Util 3 | * @Author: wyw 4 | * @Date: 2016-06-23 16:10:03 5 | */ 6 | var _ = require('lodash'); 7 | var Logger = {}; 8 | 9 | var LEVELS = ['error', 'warn', 'info', 'log']; 10 | 11 | // default 12 | var _use = true, 13 | _levelIndex = LEVELS.indexOf('warn'), 14 | _logger = console, _logfun; 15 | 16 | if(process.argv.indexOf('--debug') > 0 || process.argv.indexOf('--debug-brk') > 0){ 17 | _levelIndex = LEVELS.indexOf('log'); 18 | } 19 | 20 | LEVELS.forEach(function(level){ 21 | Logger[level] = function(){ 22 | if(_use) { 23 | var index = LEVELS.indexOf(level); 24 | if ( index > -1 && index <= _levelIndex ){ 25 | 26 | if(_logfun){ 27 | _logfun( ([]).join.call(arguments, '\t'), level); 28 | } else { 29 | _logger[level].apply(_logger, arguments); 30 | } 31 | } 32 | } 33 | } 34 | }) 35 | 36 | Logger.set = function(use, level, funOrLogger) { 37 | _use = use; 38 | if(level && LEVELS.indexOf(level) > -1){ 39 | _levelIndex = LEVELS.indexOf(level); 40 | } 41 | if( _.isFunction(funOrLogger)){ 42 | _logfun = funOrLogger; 43 | } else if( _.isObject(funOrLogger)){ 44 | _logger = funOrLogger; 45 | } 46 | } 47 | 48 | module.exports = Logger; -------------------------------------------------------------------------------- /lib/annotation/load/AutoLoad.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: xin.lin 3 | * @Date: 15-4-21 4 | * auto load the module 5 | * 6 | * @AutoLoad("1") 7 | */ 8 | var _SortedCol = require("../../base/_SortedCol"), 9 | utils = require("../../Utils"), 10 | Logger = require('../../Logger'); 11 | 12 | var AutoLoad = module.exports = require('../Annotation').extend({ 13 | /** 14 | * compile the model 15 | * @param {[Model]} model [annotation data] 16 | * @return {[type]} [description] 17 | */ 18 | compile: function(model) { 19 | var po = model.po(), 20 | order = parseInt(po); 21 | order = isNaN(order)? Infinity : order; 22 | AutoLoad.addSort(this, order); 23 | return order; 24 | }, 25 | execute: function(){ 26 | if(AutoLoad.exportFlag){ 27 | AutoLoad.sortAutoLoads.traverse(function(i, item, order){ 28 | Logger.info(order, item.model.classpath()); 29 | item.model.exports(false, true); 30 | }); 31 | AutoLoad.exportFlag = false; 32 | } 33 | } 34 | }, 35 | { 36 | //annotation name 37 | name: 'AutoLoad', 38 | sortAutoLoads: null, // 一个有序数组,会在插入时整理顺序 39 | exportFlag: true, // autoload模块是否需要被加载,为false表示已被加载完无需重复加载 40 | addSort: function(item, order){ 41 | if(!this.sortAutoLoads){ 42 | this.sortAutoLoads = new _SortedCol(true); 43 | } 44 | this.sortAutoLoads.add(item, order); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /doc/Proxy.md: -------------------------------------------------------------------------------- 1 | 2 | Proxy 3 | ===== 4 | 模块的代理,可以介入到模块方法的执行阶段,但业务操作的时候一般要通过instance()获取原始模块实例再进行操作。**被代理的模块不会立即被加载进来,只有在用的时候才会加载。** 5 | ## 方法 6 | * addMethodInterceptor 7 | 参数: 8 | 9 | | 参数名 | 类型 | 说明 | 10 | | ----------- | :--------------------------: | --------: | 11 | | method | String | 需要介入的方法名 | 12 | | advice | [PROXYCYCLE](##PROXTYCYCLE) | 介入阶段 | 13 | | interceptor | [INTERCEPTOR](##INTERCEPTOR) | 需要介入的方法类型 | 14 | | callback | Function | 回调函数 | 15 | 16 | * instance 17 | 获取原始模块实例 18 | 19 | ## 例子 20 | ``` 21 | /*@AutoLoad*/ 22 | var nodeAnnotation = require('node-annotation'); 23 | 24 | module.exports = nodeAnnotation.Annotation.extend({ 25 | /** 26 | * the annotation affect 27 | * @return {[void]} 28 | */ 29 | execute: function() { 30 | var model = this.model; 31 | model.exports().addMethodInterceptor(model.vo(), nodeAnnotation.PROXYCYCLE.BEFORE, function(){ 32 | //do something here 33 | }); 34 | } 35 | }, { 36 | //annotation name 37 | name: "Example" 38 | }); 39 | ``` 40 | 41 | ## PROXYCYCLE 42 | 43 | 模块实例方法介入时机枚举 44 | 45 | - BEFORE(0) 方法执行前 46 | - AFTERRETURNING(1) 方法入参的回调执行前 47 | - AFTER(2) 方法执行后 48 | - AROUND(3) 方法执行前后,BEFORE后,AFTER前的阶段 49 | - THROWS(4) 方法抛出异常时 50 | 51 | ## INTERCEPTOR 52 | 53 | 模块介入的方法的类型枚举 54 | 55 | - MODULE(0) 介入模块的每个方法 56 | - METHOD(1) 介入普通模块的方法或类模块的静态方法 57 | - PROTORTYPE(2) 介入类模块的成员方法(实际是在模块输出对象的原型链上寻找方法) -------------------------------------------------------------------------------- /lib/annotation/controller/ResponseBody.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-10 4 | * control the request and response 5 | */ 6 | /*@AutoLoad*/ 7 | var PROXYCYCLE = require('../../PROXYCYCLE'); 8 | module.exports = require("./RequestMapping").extend({ 9 | /** 10 | * automatic transfer json to string 11 | * @return {[type]} [description] 12 | */ 13 | execute: function() { 14 | var model = this.model, 15 | voParam = model.voParam(), 16 | resIndex; 17 | voParam.some(function(item, i) { 18 | if (item == 'res' || item == 'response') { 19 | resIndex = i; 20 | return true; 21 | } 22 | }); 23 | model.exports().addMethodInterceptor(model.vo(), PROXYCYCLE.BEFORE, function() { 24 | if (typeof resIndex !== 'undefined') { 25 | var res = arguments[resIndex], 26 | old = res.end; 27 | res.end = function() { 28 | if (typeof arguments[0] == 'object') { 29 | arguments[0] = JSON.stringify(arguments[0]); 30 | } 31 | old.apply(res, arguments); 32 | }; 33 | } 34 | }); 35 | }, 36 | /** 37 | * compile the model 38 | * @param {[Model]} model [annotation data] 39 | * @return {[type]} [description] 40 | */ 41 | compile: function(model) { 42 | model.exports(); 43 | } 44 | }, { 45 | //annotation name 46 | name: "ResponseBody" 47 | }); 48 | -------------------------------------------------------------------------------- /lib/base/_Map.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-12 4 | * Map 5 | */ 6 | 7 | module.exports = Map; 8 | function Map(){} 9 | 10 | Map.prototype = { 11 | constructor: Map, 12 | /** 13 | * add key and value 14 | * @param {[type]} key [description] 15 | * @param {[type]} value [description] 16 | */ 17 | add: function(key, value) { 18 | return this.map()[key] = value; 19 | }, 20 | /** 21 | * get the value of key 22 | * @param {[Int]} index [item] 23 | * @return {[type]} [description] 24 | */ 25 | get: function(key){ 26 | return this.map()[key]; 27 | }, 28 | /** 29 | * delete the key 30 | * @param {[Int]} index [item] 31 | * @return {[type]} [description] 32 | */ 33 | del: function(key){ 34 | this._map[key] = null; 35 | }, 36 | /** 37 | * get the map 38 | * @return {[type]} [description] 39 | */ 40 | map: function() { 41 | if (!this._map) 42 | this._map = {}; 43 | return this._map; 44 | }, 45 | /** 46 | * traverse through the map 47 | * @param {Function} fn [description] 48 | * @param {[type]} context [description] 49 | * @return {[type]} [description] 50 | */ 51 | traverse: function(fn, context) { 52 | var map = this.map(); 53 | for (var attr in map) { 54 | if(fn.call(context || this, attr, map[attr]) === false)break; 55 | } 56 | }, 57 | /** 58 | * doesn't have child 59 | * @return {Boolean} [description] 60 | */ 61 | isEmpty: function(){ 62 | var has = false; 63 | for(var attr in map){ 64 | has = true; 65 | break; 66 | } 67 | return has; 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /doc/annotation/Configure.md: -------------------------------------------------------------------------------- 1 | # Configure 2 | ## 设置配置文件路径 3 | 默认配置文件路径为 **启动进程所在路径 + resource** 目录,可通过 4 | 5 | ```require('node-annotation').configurePath(指定目录)``` 6 | 7 | 来指定目录 8 | 9 | ## 参数 10 | 支持两种参数形式 11 | - 文件路径|变量名,每个文件的变量都是单独的,不会有命名冲突 12 | * 文件路径是指相对于上面配置文件目录的路径。 13 | * 变量名是指变量路径。 14 | - 变量名 15 | 变量路径,这里会将所有配置文件里的内容进行合并,当命名冲突产生,合并的为 16 | * 当值为简单类型时会被覆盖 17 | * 当值为复杂对象(非数组类型)时会取并集 18 | * 当值为复杂对象(数组类型)时会进行concat操作 19 | 比如: 20 | ``` 21 | {"data": [{ "user": "barney" }, { "user": "fred" }]} 和 22 | {"data": [{ "age": 36 }, { "age": 40 }]} 会得到 23 | { "data": [{ "user": "barney", "age": 36 }, { "user": "fred", "age": 40 }] } 24 | ``` 25 | ``` 26 | {"fruits": ["apple"], "vegetables": ["beet"]} 和 27 | {"fruits": ["banana"],"vegetables": ["carrot"]} 会得到 28 | { "fruits": ["apple", "banana"], "vegetables": ["beet", "carrot"] } 29 | ``` 30 | 31 | ## 实例 32 | > settings.json文件,在配置文件目录下 33 | 34 | ``` 35 | { 36 | "monitor": { 37 | "server": "xxx", 38 | "port": "8125", 39 | "prefix": "nodeExample.", 40 | "interval": 1000 41 | }, 42 | "appenders": [{ 43 | "type": "Hello" 44 | }, { 45 | "type": "World" 46 | }] 47 | } 48 | ``` 49 | > User.js 50 | ``` 51 | ... 52 | /*@Configure("setting.json|appenders[0].type")*/ 53 | settings: null, 54 | ... 55 | ``` 56 | 或者 57 | ``` 58 | ... 59 | /*@Configure("appenders[0].type")*/ 60 | settings: null, 61 | ... 62 | ``` 63 | settings的值为Hello 64 | 65 | 66 | > 整个Demo参见[node-annotation-example](https://www.npmjs.com/package/node-annotation-example) 中的example/webApp/app.js 67 | -------------------------------------------------------------------------------- /lib/annotation/controllerAdvice/ControllerAdvice.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Description 3 | * @Author: wyw 4 | * @Date: 2016-06-08 10:23:06 5 | */ 6 | 7 | /*@AutoLoad*/ 8 | var Path = require('path'), 9 | PROXYCYCLE = require('../../PROXYCYCLE'), 10 | INTERCEPTOR = require('../../INTERCEPTOR'), 11 | ApplicationContext = require("../../ApplicationContext"); 12 | 13 | var ControllerAdvice = module.exports = require("../Annotation").extend({ 14 | execute: function() { 15 | this.traverse(function(i, item){ 16 | var name = item.model.name(); 17 | switch(name){ 18 | case 'ExceptionHandler': 19 | ControllerAdvice.exceptionHandlers.push(item.data); 20 | break; 21 | default: 22 | // do nothing 23 | break; 24 | } 25 | }, this); 26 | } 27 | }, { 28 | //annotation name 29 | name: "ControllerAdvice", 30 | exceptionHandlers: [] 31 | }); 32 | 33 | ApplicationContext.getBean(Path.join(__dirname, '../controller/Controller')) 34 | .then(function(Controller){ 35 | Controller.addMethodInterceptor('handlerException', 36 | PROXYCYCLE.AFTER, 37 | INTERCEPTOR.PROTOTYPE, 38 | function(breakNext, err, req, res){ 39 | if(!breakNext){ 40 | _.forEach(ControllerAdvice.exceptionHandlers, function(el){ 41 | if(el.match(err)){ 42 | el.fun(err, req, res); 43 | return false; 44 | } 45 | }); 46 | } 47 | }); 48 | }) 49 | -------------------------------------------------------------------------------- /lib/base/_Collection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-10 4 | * Collection 5 | */ 6 | var Iterator = require("./_Iterator"); 7 | 8 | module.exports = Collection; 9 | 10 | function Collection(){}; 11 | 12 | Collection.prototype = { 13 | constructor: Collection, 14 | /** 15 | * add item 16 | * @param {[type]} item [description] 17 | */ 18 | add: function(item) { 19 | this.collection().push(item); 20 | return item; 21 | }, 22 | /** 23 | * get the item 24 | * @param {[Int]} index [item] 25 | * @return {[type]} [description] 26 | */ 27 | get: function(index){ 28 | return this.collection()[index]; 29 | }, 30 | /** 31 | * return the last instance 32 | * @return {[type]} [description] 33 | */ 34 | current: function(){ 35 | var collection = this.collection(); 36 | return collection[collection.length - 1]; 37 | }, 38 | /** 39 | * fetch all children in collection 40 | * @return {[type]} [description] 41 | */ 42 | collection: function() { 43 | if (!this._children) 44 | this._children = []; 45 | return this._children; 46 | }, 47 | /** 48 | * traverse all children 49 | * @param {Function} fn [description] 50 | * @param {[type]} context [description] 51 | * @return {[type]} [description] 52 | */ 53 | traverse: function(fn, context) { 54 | var collection = this.collection(), len = collection.length; 55 | for (var i = 0; i < len; i++) { 56 | if(fn.call(context || this, i, collection[i]) === false)break; 57 | } 58 | }, 59 | /** 60 | * collection iterator 61 | * @return {[type]} [description] 62 | */ 63 | iterator: function(){ 64 | return new Iterator(this.collection()); 65 | }, 66 | /** 67 | * doesn't have child 68 | * @return {Boolean} [description] 69 | */ 70 | isEmpty: function(){ 71 | return this.collection().length == 0; 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /lib/annotation/aop/PointCut.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-12-09 4 | * point cut of aop 5 | */ 6 | /*@AutoLoad("0")*/ 7 | var Path = require('path'); 8 | 9 | var ApplicationContext = require('../../ApplicationContext'), 10 | INTERCEPTOR = require('../../INTERCEPTOR'); 11 | module.exports = require("./Aspect").extend({ 12 | /** 13 | * 14 | * @return {[type]} [description] 15 | */ 16 | execute: function() { 17 | var data = this.data, 18 | watchers = []; 19 | this.traverse(function(i, watcher) { 20 | watchers.push({ 21 | advice: watcher.getAdvice(), 22 | method: data.method, 23 | interceptor: data.interceptor?INTERCEPTOR[data.interceptor.toUpperCase()]:INTERCEPTOR.METHOD, 24 | callback: function() { 25 | var instance = watcher.model.exports(true); 26 | return instance[watcher.model.vo()].apply(instance, arguments); 27 | } 28 | }); 29 | }); 30 | if (watchers.length == 0) return; 31 | ApplicationContext.addExpress(data.module, watchers); 32 | }, 33 | /** 34 | * the annotation affect 35 | * @return {[type]} [description] 36 | */ 37 | affectAnnotation: function() { 38 | this.execute(); 39 | }, 40 | /** 41 | * compile the model 42 | * @param {[Model]} model [annotation data] 43 | * @return {[type]} [description] 44 | */ 45 | compile: function(model) { 46 | var po = model.po(); 47 | if (po && po.module) { 48 | //in window path is \, but others are \ 49 | po.module = RegExp(po.module.replace('/', Path.sep)); 50 | } 51 | return po; 52 | } 53 | }, { 54 | //annotation name 55 | name: "PointCut" 56 | }); 57 | -------------------------------------------------------------------------------- /doc/annotation/fiber.md: -------------------------------------------------------------------------------- 1 | # Fiber 2 | 3 | > 类似ES7的async/awatit语法 4 | 5 | 6 | 7 | ## Async 8 | 9 | 该注解可修饰一个函数,被其修饰的函数将成为一个异步函数,其内部可以采用同步方式调用其他异步函数(使用.await,见下方)。 10 | 11 | ### Demo 12 | 13 | ```javascript 14 | var fs = require('./SyncLib').fs, 15 | path = require('path'); 16 | module.exports = { 17 | /*@AsyncWrap*/ 18 | readFileSync: require('fs').readFile, 19 | /*@AsyncWrap*/ 20 | sleep: function(ms, cbk){ 21 | setTimeout(cbk, ms); 22 | }, 23 | /*@Async*/ 24 | do1: function(ms){ 25 | var a = this.readFileSync(path.join(__dirname, './text.txt')).await(); 26 | this.sleep(ms).await(); 27 | return a.toString(); 28 | }, 29 | /*@Async*/ 30 | do2: function(file){ 31 | var a = fs.readFileSync(file).await(); 32 | return a.toString(); 33 | }, 34 | /*@Async*/ 35 | fin: function(ms){ 36 | var a1 = this.do1(ms).await(); 37 | var a2 = this.do2(path.join(__dirname, './main.js')).await(); 38 | return a1; 39 | } 40 | } 41 | ``` 42 | 43 | 44 | 45 | ## await/then 46 | 47 | 一个Async函数有两种使用方式: 48 | 49 | - 在另一个Async函数中同步调用,调用起.await直接赋值给一个变量即可。 50 | 51 | 参见上个Demo 52 | 53 | ​ 54 | 55 | - 在最外层(因为最外层的函数一定不是一个Async函数,而是一个普通函数),Async函数的结果可以作为一个Promise来使用,调用起.then方法完成后续处理。 56 | 57 | ```javascript 58 | 59 | var main = require('./use.js');// main引用的为上面Demo 60 | 61 | main.fin(3000).then(function(val){ 62 | console.log(val); 63 | }, function(err){ 64 | console.error(err, err.stack) 65 | }) 66 | ``` 67 | 68 | ​ 69 | 70 | ​ 71 | 72 | ## AsyncWrap 73 | 74 | 该注解修饰一个对象,将其中的callback形式的异步函数转化为Async函数 75 | 76 | ### 参数 77 | 78 | - `multi` 指示一个函数将在err参数后返回不止一个参数,比如`child_process.exec()`,此时将取最后一个参数作为callback 79 | - type: Boolean 80 | - default: false 81 | - `suffix` 函数后缀,新转化的函数通过后缀与原函数区别开来,均可使用 82 | - type: String 83 | - default: “Sync” 84 | - `stop` 类似 `request`的模块返回的函数本身包含函数成员,阻止此类的递归包裹。 85 | - type: Boolean 86 | - default: false 87 | 88 | ### Demo 89 | 90 | ```javascript 91 | module.exports = { 92 | /*@AsyncWrap*/ 93 | fs: require('fs'), 94 | /*@AsyncWrap({stop:true})*/ 95 | request: require('request') 96 | } 97 | ``` 98 | 99 | > 整个Demo参见[node-annotation-example](https://www.npmjs.com/package/node-annotation-example) 中的example/fiber -------------------------------------------------------------------------------- /lib/annotation/configure/Configure.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 16-01-04 4 | * load the configure file 5 | * Configure('settings') or Configure('file path relative to configure path | settings') 6 | * 同步加载全部config 7 | */ 8 | 'use strict'; 9 | /*@AutoLoad*/ 10 | var Path = require('path'), 11 | fs = require('fs'), 12 | _ = require('lodash'), 13 | Logger = require('../../Logger'), 14 | ApplicationContext = require('../../ApplicationContext'), 15 | Utils = require('../../Utils'); 16 | 17 | var _configs = {}, _fileConfigs = {}; 18 | var Configure = module.exports = require("../Annotation").extend({ 19 | /** 20 | * the annotation affect 21 | * @return {[type]} [description] 22 | */ 23 | execute: function() { 24 | var model = this.model, 25 | po = model.po(), 26 | proxy = model.exports(); 27 | 28 | if (!po){ 29 | proxy.instance()[model.vo()] = _configs; 30 | return; 31 | } 32 | 33 | var settings = po.split('|'), configs = _configs; 34 | if(settings.length == 2){ 35 | configs = _fileConfigs[settings[0]]; 36 | po = settings[1]; 37 | } 38 | proxy.instance()[model.vo()] = _.get(configs, po); 39 | } 40 | }, { 41 | //annotation name 42 | name: "Configure", 43 | /** 44 | * load the configure files 45 | * @return [void] 46 | */ 47 | init: function() { 48 | var configurePath = ApplicationContext.configurePath(); 49 | try { 50 | fs.accessSync(configurePath, fs.R_OK); 51 | } catch (e) { 52 | Logger.warn('未配置resource文件夹或没有读取权限,@Configure注解将无法生效'); 53 | return; 54 | } 55 | Utils.recursiveDirsSync(configurePath, function(content, filename) { 56 | try { 57 | _configs = _.merge(_configs, (_fileConfigs[filename.replace(configurePath + Path.sep, "")] = JSON.parse(content)), function(arry1, arry2) { 58 | if (_.isArray(arry1)) { 59 | return arry1.concat(arry2); 60 | } 61 | }); 62 | } catch (e) { 63 | Logger.error(e); 64 | } 65 | }) 66 | Logger.log("全部配置项:\n",_configs,_fileConfigs) 67 | } 68 | }); 69 | Configure.init(); 70 | -------------------------------------------------------------------------------- /lib/base/_SortedCol.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Description 3 | * @Author: wyw 4 | * @Date: 2016-06-12 11:58:00 5 | */ 6 | 7 | var Iterator = require("./_Iterator"); 8 | 9 | module.exports = SortedCol; 10 | 11 | function SortedCol(order){ 12 | // defalut order is asc(when equal, donot change orginal order) 13 | // if order is set to true,then desc 14 | this.comp = order? 15 | function(a, b){ 16 | return a >= b; 17 | }:function(a, b){ 18 | return a < b; 19 | } 20 | }; 21 | 22 | SortedCol.prototype = { 23 | constructor: SortedCol, 24 | /** 25 | * add item 26 | * @param {[type]} item [description] 27 | * @param {[integer]} weight [order weight of the item] 28 | */ 29 | add: function(item, weight) { 30 | var weight = (isNaN(weight))?Infinity:weight; 31 | var it = { 32 | item: item, 33 | weight: weight 34 | }; 35 | var collection = this.collection(), len = collection.length; 36 | for (var i = 0; i < len; i++) { 37 | if( this.comp(collection[i].weight, it.weight) ){ 38 | break; 39 | } 40 | } 41 | this.collection().splice(i, 0, it); 42 | return it; 43 | }, 44 | /** 45 | * get the item 46 | * @param {[Int]} index [item] 47 | * @return {[type]} [description] 48 | */ 49 | get: function(index){ 50 | return this.collection()[index].item; 51 | }, 52 | /** 53 | * return the last instance 54 | * @return {[type]} [description] 55 | */ 56 | current: function(){ 57 | var collection = this.collection(); 58 | return collection[collection.length - 1].item; 59 | }, 60 | /** 61 | * fetch all children in collection 62 | * @return {[type]} [description] 63 | */ 64 | collection: function() { 65 | if (!this._children) 66 | this._children = []; 67 | return this._children; 68 | }, 69 | /** 70 | * traverse all children 71 | * @param {Function} fn [description] 72 | * @param {[type]} context [description] 73 | * @return {[type]} [description] 74 | */ 75 | traverse: function(fn, context) { 76 | var collection = this.collection(), len = collection.length; 77 | for (var i = 0; i < len; i++) { 78 | if(fn.call(context || this, i, collection[i].item, collection[i].weight) === false)break; 79 | } 80 | }, 81 | /** 82 | * collection iterator 83 | * @return {[type]} [description] 84 | */ 85 | iterator: function(){ 86 | return new Iterator(this.collection()); 87 | }, 88 | /** 89 | * doesn't have child 90 | * @return {Boolean} [description] 91 | */ 92 | isEmpty: function(){ 93 | return this.collection().length == 0; 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /lib/ApplicationContext.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Promise = require('promise'), 3 | Path = require('path'), 4 | _ = require('lodash'), 5 | Logger = require('./Logger'); 6 | 7 | var _beanFactory = require('./proxy/ProxyFactory'), 8 | _push = ([]).push, 9 | _app, _resolve, 10 | _configurePath = Path.join(process.cwd(), 'resources'), 11 | _golbalErrorHandler = function(err){ 12 | if(err instanceof Error){ 13 | throw err; 14 | } else { 15 | throw new Error(JSON.stringify(err)); 16 | } 17 | } 18 | 19 | module.exports = { 20 | /** 21 | * get the app for web application 22 | * @param {[Server]} app 23 | * @return {[Promise]} 24 | */ 25 | app: function(app) { 26 | if (!_app) { 27 | _app = new Promise(function(resolve) { 28 | _resolve = resolve; 29 | }); 30 | } 31 | if (app) { 32 | _resolve(app); 33 | } 34 | return _app; 35 | }, 36 | /** 37 | * get bean by name user customize or file path relative to project 38 | * @param {[String]} name [name user customize or file path] 39 | * @return {[Promise]} 40 | */ 41 | getBean: function(name) { 42 | return _beanFactory.getBean(name); 43 | }, 44 | /** 45 | * create bean 46 | * @param {[String]} name [alias name of module user customize] 47 | * @param {[String]} path [path of module relatvie to project root] 48 | * @return {[Proxy|Object]} 49 | */ 50 | createBean: function(name, path) { 51 | return _beanFactory.createBean(name, path); 52 | }, 53 | /** 54 | * add express to filter all modules whether or not would be a proxy module 55 | * @param {[String]} regular [regular to match path and methods of module] 56 | * @param {[Array<{advice, method, callback}>]} watchers 57 | * @return {[void]} 58 | */ 59 | addExpress: function(regular, watch) { 60 | _beanFactory.addExpress(regular, watch); 61 | }, 62 | /** 63 | * which directionary is configure files in 64 | * @param {[type]} path [description] 65 | * @return {[type]} [description] 66 | */ 67 | configurePath: function(path) { 68 | if (path) { 69 | _configurePath = path; 70 | } 71 | return _configurePath; 72 | }, 73 | /** 74 | * get the global error handler 75 | * @return {[type]} [description] 76 | */ 77 | getGlobalErrorHandler: function(){ 78 | return _golbalErrorHandler; 79 | }, 80 | /** 81 | * set the global error handler 82 | * @param {[function]} fun [user customize golbal error handler function] 83 | */ 84 | setGlobalErrorHandler: function(fun){ 85 | _golbalErrorHandler = fun; 86 | }, 87 | /** 88 | * [setLogger description] 89 | * @param {[boolean]} use [set on/off of logger] 90 | * @param {[string]} level [log,info,warn,error] 91 | * @param {[type]} funOrLogger [if its a function, will receive the log string and level, 92 | * else if you use a logger like log4js, give a Object with the same behavior of console] 93 | */ 94 | setLogger: function(use, level, funOrLogger) { 95 | Logger.set(use, level, funOrLogger); 96 | } 97 | }; 98 | -------------------------------------------------------------------------------- /lib/base/Class.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | var Base = module.exports = function() {}; 4 | Base.extend = extend; 5 | 6 | /** 7 | * Extend this Class to create a new one inherithing this one. 8 | * Also add a helper __super__ object poiting to the parent prototypes methods 9 | * @param {Object} protoProps Prototype properties (available on the instances) 10 | * @param {Object} staticProps Static properties (available on the contructor) 11 | * @return {Object} New sub class 12 | */ 13 | function extend(protoProps, staticProps) { 14 | var parent = this; 15 | var child; 16 | 17 | // The constructor function for the new subclass is either defined by you 18 | // (the "constructor" property in your `extend` definition), or defaulted 19 | // by us to simply call the parent's constructor. 20 | if (protoProps && _.has(protoProps, 'constructor')) { 21 | child = protoProps.constructor; 22 | } else { 23 | child = function() { 24 | return parent.apply(this, arguments); 25 | }; 26 | } 27 | _.forEach(staticProps, function(v, k) { 28 | if (typeof v == 'function') { 29 | v.$name = k; 30 | } 31 | }); 32 | // Add static properties to the constructor function, if supplied. 33 | _.extend(child, parent, staticProps); 34 | 35 | // Set the prototype chain to inherit from `parent` 36 | child.prototype = Object.create(parent.prototype, { 37 | constructor: { 38 | value: child, 39 | enumerable: false, 40 | writable: true, 41 | configurable: true 42 | } 43 | }); 44 | //call the same method in the prototype chain 45 | child.prototype.super = function() { 46 | var parent = this.constructor, 47 | caller = arguments.callee.caller && arguments.callee.caller, 48 | args = arguments.length > 0 ? arguments : caller.arguments, 49 | method = caller.$name || "extend"; 50 | if (!parent) return; 51 | if (parent[method]) { 52 | return parent[method].apply(this, args); 53 | } 54 | while (parent = parent.constructor) { 55 | if (parent[method]) { 56 | return parent[method].apply(this, args); 57 | } 58 | } 59 | }; 60 | // Add prototype properties (instance properties) to the subclass, 61 | // if supplied. 62 | if (protoProps) { 63 | _.forEach(protoProps, function(v, k) { 64 | if (typeof v == 'function') { 65 | v.$name = k; 66 | } 67 | }); 68 | _.extend(child.prototype, protoProps); 69 | } 70 | 71 | // Set a convenience property in case the parent's prototype is needed 72 | // later. 73 | child.__super__ = parent.prototype; 74 | child.super = function() { 75 | var parent = child.__super__ && child.__super__.constructor, 76 | caller = arguments.callee.caller && arguments.callee.caller, 77 | args = arguments.length > 0 ? arguments : caller.arguments, 78 | method = caller.$name; 79 | if (!parent) return; 80 | if (parent[method]) { 81 | return parent[method].apply(this, args); 82 | } 83 | while (parent = parent.__super__ && parent.__super__.constructor) { 84 | if (parent[method]) { 85 | return parent[method].apply(this, args); 86 | } 87 | } 88 | } 89 | return child; 90 | }; 91 | -------------------------------------------------------------------------------- /doc/annotation/Controller.md: -------------------------------------------------------------------------------- 1 | # Controller 2 | 3 | > 控制器相关,继承自 [Component](./DI.md),在该注解下才可以使用下述的子注解 4 | 5 | ## RequestMapping 6 | 7 | > 作用类似于express的router中间件,被注解的函数将收到注解描述的url规则的请求 8 | 9 | ### 路由规则及优先级 10 | 11 | - 路由匹配的优先级为 12 | 13 | 纯字符串路由(eg: /goods/aaa) > 一级前缀路由(eg:/goods/{name}) > 纯正则路由(eg: {url:"^goods\\\/[a-z]*$",useExp:true}) 14 | 15 | - 同一优先级内按书写先后顺序。 16 | 17 | - 跨controller的优先级按controller加载顺序。 18 | 19 | - 对于任意路由规则都没有匹配到时,如果你定义了路径为“/404”的路由规则,则301重定向过去,否则,直接返回404 NotFound。 20 | 21 | ### 注解参数 22 | 23 | | URL Pattern | 功能 | 24 | | ---------------------------------------- | ---------------------------------------- | 25 | | /goods | 仅匹配该url | 26 | | /goods/{name} | 匹配该路由,并将{name}占位符对应的参数作为请求处理函数中名为`name`的形参传入 | 27 | | /regParam/{code=[1-9]*ab} | 匹配该路由,并将=后面的正则匹配的参数作为请求处理函数中名为`code`的形参传入 | 28 | | /multiReg/v-{code=00[1-9]*ab}-t-{type=[A-Z]+0} | 同上,正则匹配参数不仅可以在/之间,还可以在其内,并可食用多个 | 29 | | ^\\\/re[g]?$ (需要配置useExp: true) | 匹配正则对应的url | 30 | | {url: "/goods", method: "post"} | 仅匹配post的该url请求 | 31 | 32 | 33 | 34 | | 参数类型 | 举例 | 说明 | 35 | | ------ | ---------------------------------------- | ---------- | 36 | | String | @RequestMapping("/goods") | - | 37 | | Object | @RequestMapping({ url : "/goods",method : "post"})
@RequestMapping({url:"^\\\/re[g]?$", useExp:true}) | 满足对象的所有条件 | 38 | | Array | @RequestMapping([{url: "/animals", method: "post"},{url: "/allAnimals"}])
@RequestMapping(["/goods", "/ajax/goods/{name}"] | 匹配数组中的任一规则 | 39 | 40 | ### 处理函数参数 41 | 42 | > 是通过传入的形参的名称来做参数注入的,与参数位置无关 43 | 44 | | name | value | 45 | | ----------------------------------- | ---------------------------------------- | 46 | | req/request | express.requset | 47 | | res/reponse | exrepss.reponse | 48 | | reqData | request.body + request.query(不区分post/get方式取参数) | 49 | | other(urlPattern中通过形如{name}描述的参数注入) | {name}占位或{name=XX}匹配的参数 | 50 | 51 | ## ResponseBody 52 | 53 | > 修饰请求处理参数中res.end的返回,将对象作一遍JSON.stringify包裹 54 | 55 | ## ExceptionHandler 56 | 57 | > 错误处理函数,可以捕获请求处理函数内的同步以及异步错误 58 | 59 | ### 注解参数 60 | 61 | - 无参数:将处理该controller下的所有请求处理函数的异常 62 | - value型:将处理Error.msg=value的错误(因为正常的new Error(msg)中仅能向Error传入一个参数作为Error的msg) 63 | - key=value型:将处理Error[key]=value的错误(系统错误通常会带上code,例如找不到文件这一错误会是code=ENOENT) 64 | 65 | ### 处理函数参数 66 | 67 | 将传入三个参数,依次为异常的Error对象,请求request,请求response 68 | 69 | ### 注意事项 70 | 71 | - 有一种情形下的异步错误无法捕获:在请求处理函数内调用了在其外定义的静态异步方法 72 | 73 | - 对于Promise,由于Promise内的异常被Promise本身捕获了,如果希望暴露出来,推荐改写/追加(nodejs原生的Promise无done方法,一般Promise/A+库均包含)的done方法: 74 | 75 | ```javascript 76 | Promise.prototype.done = function (onFulfilled, onRejected) { 77 | var self = arguments.length ? this.then.apply(this, arguments) : this 78 | if (process.domain) { 79 | process.domain.emit('error', err) 80 | } else { 81 | self.then(null, function (err) { 82 | setTimeout(function () { 83 | throw err 84 | }, 0) 85 | }) 86 | } 87 | } 88 | ``` 89 | 90 | 然后在希望抛出错误的Promise最后调用.done()方法。 91 | 92 | # ControllerAdvice 93 | 94 | > 控制器增强,可以在其内使用@ExceptionHandler,将把其内的的错误处理应用于全部的@Controller 95 | > 96 | > 推荐在这里捕获所有错误,完成返回500服务器错误等操作。 97 | 98 | 99 | 100 | > 整个Demo参见[node-annotation-example](https://www.npmjs.com/package/node-annotation-example) 中的example/webApp -------------------------------------------------------------------------------- /doc/annotation/AOP.md: -------------------------------------------------------------------------------- 1 | # AOP 2 | > AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 3 | 4 | ## 概念 5 | * Aspect(切面) 一个对关注点处理的模块化。 6 | 7 | * PointCut(切入点) 明确需要介入的模块执行的点,目前只支持方法执行时机,参数为 8 | 9 | ```{module:"模块匹配正则,主要匹配模块路径", method:"方法名",interceptor:"如果module的输出是一个class,需要监听其原型方法亦其类成员方法,则此处填写prototype"}``` 10 | 11 | 当方法不存在的时候会监听所有方法。 12 | 13 | * Advice(通知点) 通知类型目前支持"Before"、"After"、"Around","Throws","AfterReturning"。 14 | 15 | ## 参数 16 | 17 | > 传入给处理函数的参数 18 | 19 | - Before 将在原函数执行之前执行 20 | - 原函数的所有参数 21 | - Around 将包裹原函数 22 | - 原函数的所有参数 23 | - 最后一个参数为原函数本身(不调用的话原函数将不会执行) 24 | - After 将在原函数return之后执行 25 | - 第一个参数为原函数的return值 26 | - 第二个及之后的参数对应原函数的所有参数 27 | - Throws 将在原函数抛出异常时执行(可以捕获大多数异步异常,参见trycatch) 28 | - 第一个参数为抛出的异常(Error) 29 | - 第二个及之后的参数对应原函数的所有参数 30 | - AfterReturning 将在原函数的入参中的回调函数执行前执行 31 | - 原函数的所有参数 32 | 33 | ## 实例 34 | 35 | Knight.js 36 | ```javascript 37 | module.exports = { 38 | hit: function() { 39 | var self = this; 40 | console.info('knight hitting!!'); 41 | process.nextTick(function() { 42 | self.flight(function() { 43 | console.info('knight flight'); 44 | }); 45 | }); 46 | this.die(new Date(), function(){ 47 | require('fs').readFile('./NotExistFIle'); 48 | }); 49 | return "result"; 50 | }, 51 | flight: function(cb) { 52 | console.info('knight ready to flight, wait for 1000 ms'); 53 | setTimeout(function() { 54 | cb(); 55 | }, 1000); 56 | }, 57 | die: function(time, cbk){ 58 | // console.log('knight die!') 59 | // throw new Error('knight die'); 60 | // cbk(); 61 | 62 | setTimeout(function() { 63 | console.log('knight die!') 64 | throw new Error('knight die'); 65 | },1000); 66 | } 67 | }; 68 | ``` 69 | Poet.js 70 | ```javascript 71 | /*@Aspect*/ 72 | module.exports = { 73 | /*@PointCut({module:"\/Knight$", method:"hit"})*/ 74 | /*@Before*/ 75 | say: function() { 76 | console.info('Poet say: knight will hit!!'); 77 | }, 78 | /*@Around*/ 79 | around: function(fn){ 80 | console.info('Poet say: around before'); 81 | var rs = arguments[arguments.length - 1 ](); 82 | console.info(rs); 83 | console.info('Poet say: around leave'); 84 | return rs; 85 | }, 86 | /*@Around*/ 87 | around2: function(fn){ 88 | console.info('Poet say: around before2'); 89 | var rs = arguments[arguments.length - 1 ](); 90 | console.info(rs); 91 | console.info('Poet say: around leave2'); 92 | return rs; 93 | }, 94 | /*@After*/ 95 | result: function(){ 96 | console.info('Poet say: knight hitted!!'); 97 | }, 98 | /*@Throws*/ 99 | hidThrow: function(err){ 100 | console.info('Poet say: knight hit wrong!!', err); 101 | }, 102 | /*@PointCut({module:"\/Knight$", method:"flight"})*/ 103 | /*@AfterReturning*/ 104 | flight: function(){ 105 | console.info('Poet say: knight will flight!!'); 106 | }, 107 | /*@PointCut({module:"\/Knight$", method:"die"})*/ 108 | /*@Throws*/ 109 | die: function(err, time){ 110 | console.info('Poet say: knight die error!!'); 111 | //console.error(time, err, err.stack); 112 | } 113 | }; 114 | ``` 115 | Main.js 116 | ```javascript 117 | var nodeAnnotation = require('node-annotation'); 118 | nodeAnnotation.start(__dirname, function() { 119 | var knight = require('./Knight'); 120 | knight.hit(); 121 | }); 122 | ``` 123 | 124 | > 整个Demo参见[node-annotation-example](https://www.npmjs.com/package/node-annotation-example) 中的example/AOP 125 | 126 | -------------------------------------------------------------------------------- /lib/Utils.js: -------------------------------------------------------------------------------- 1 | var Path = require("path"); 2 | var fs = require("fs"); 3 | var Promise = require("promise"); 4 | var toString = ({}).toString, 5 | slice = [].slice; 6 | var Utils = module.exports = { 7 | /** 8 | * recursive the directory, all is async 9 | * @param {[type]} classpath [description] 10 | * @param {Function} callback [description] 11 | * @param {Function} errCallback [description] 12 | * @param {JSON} excludeDir [description] 13 | * @return {[type]} [description] 14 | */ 15 | recursiveDirs: function(classpath, callback, errCallback, excludeDir) { 16 | if(toString.call(errCallback) == "[object Object]"){ 17 | excludeDir = errCallback; 18 | errCallback = null; 19 | } 20 | return recursive(classpath, callback); 21 | 22 | function recursive(classpath, callback) { 23 | return Promise.denodeify(fs.readdir)(classpath).then(function(files) { 24 | return Promise.all(files.map(function(file) { 25 | var tmp = Path.join(classpath, file); 26 | return Promise.denodeify(fs.stat)(tmp).then(function(stat) { 27 | if (stat.isDirectory()) { 28 | if(excludeDir && excludeDir[tmp]){ 29 | return; 30 | } 31 | return recursive(tmp, callback); 32 | } 33 | return Utils.readFile(tmp, callback, errCallback); 34 | }); 35 | })); 36 | }); 37 | } 38 | }, 39 | /** 40 | * recursive the directory, all is sync 41 | * @param {[type]} classpath [description] 42 | * @param {Function} eachHandler [description] 43 | * @param {JSON} excludeDir [description] 44 | * @return {[type]} [description] 45 | */ 46 | recursiveDirsSync: function(classpath, eachHandler, excludeDir){ 47 | recursive(classpath, eachHandler); 48 | 49 | function recursive(classpath, fn) { 50 | var files = fs.readdirSync(classpath); 51 | files.map(function(file){ 52 | var curpath = Path.join(classpath, file), 53 | stat = fs.statSync(curpath); 54 | if(stat.isDirectory()){ 55 | if(excludeDir && excludeDir[curpath]){ 56 | return; 57 | } 58 | recursive(curpath, fn); 59 | } 60 | var context = fs.readFileSync(curpath); 61 | fn(context, file); 62 | }) 63 | } 64 | }, 65 | /** 66 | * read file content 67 | * @param {[String]} filename [description] 68 | * @param {[Object]} options [description] 69 | * @param {Function} callback [description] 70 | * @param {Function} errCallback [description] 71 | * @return {[type]} [description] 72 | */ 73 | readFile: function(filename, options, callback, errCallback) { 74 | if(!Utils.typeofObject(options).isObject()){ 75 | errCallback = callback; 76 | callback = options; 77 | options = { 78 | encoding: "utf8" 79 | }; 80 | } 81 | return Promise.denodeify(fs.readFile)(filename, options).then(function(data) { 82 | callback(data, filename); 83 | }, function(err) { 84 | if(errCallback){ 85 | errCallback(err); 86 | }else{ 87 | throw err; 88 | } 89 | }); 90 | }, 91 | /** 92 | * copy Class prototype 93 | * @param {[type]} iclass [description] 94 | * @return {[type]} [description] 95 | */ 96 | copyOwnProperty: function(iclass) { 97 | var protoype = iclass.prototype, 98 | map = {}; 99 | for (var attr in protoype) { 100 | if (!iclass.hasOwnProperty(attr) && attr != "constructor") { 101 | map[attr] = protoype[attr]; 102 | } 103 | } 104 | return map; 105 | }, 106 | /** 107 | * extend object 108 | * @param {[type]} func [description] 109 | * @param {[type]} obj [description] 110 | * @return {[type]} [description] 111 | */ 112 | extend: function(func, obj) { 113 | for (var i in obj) 114 | if(obj.hasOwnProperty(i)) 115 | func[i] = obj[i]; 116 | return func; 117 | }, 118 | /** 119 | * get the type of object 120 | * @param {[type]} obj [description] 121 | * @return {[type]} [description] 122 | */ 123 | typeofObject: function(obj) { 124 | obj = toString.call(obj); 125 | return { 126 | //if it is a string, or not 127 | isString: function() { 128 | return obj == "[object String]"; 129 | }, 130 | //if it is a object, or not 131 | isObject: function() { 132 | return obj == "[object Object]"; 133 | }, 134 | //if it is a array, or not 135 | isArray: function() { 136 | return obj == "[object Array]"; 137 | }, 138 | //if it is a function, or not 139 | isFunction: function() { 140 | return obj == "[object Function]"; 141 | }, 142 | value: obj 143 | }; 144 | }, 145 | slice: function(arry, start, end){ 146 | slice.call(arry, start, end); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lib/proxy/ProxyFactory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Path = require('path'), 3 | _ = require('lodash'); 4 | 5 | var Proxy = require('./Proxy'), 6 | Express = require('./Express'), 7 | INTERCEPTOR = require('../INTERCEPTOR'), 8 | PROXYCYCLE = require('../PROXYCYCLE'); 9 | 10 | var _beans = {}, 11 | _expresses = [], 12 | _instance = null, 13 | _cwd = process.cwd(), 14 | _loader = module.require, 15 | _push = ([]).push, 16 | ABSOLUTE_PROJECT = /^\//, 17 | RELATIVE_CUR = /\.\//, 18 | EXTNAME_REG = /\.[^./]+$/; 19 | 20 | function ProxyFactory() { 21 | if (_instance) { 22 | return _instance; 23 | } 24 | _instance = this; 25 | //require delegate 26 | module.constructor.prototype.require = function(path) { 27 | var _path = Path.join(Path.dirname(this.filename), path); 28 | if (!_instance.match(_path)) { 29 | return _loader.apply(this, arguments); 30 | } 31 | return _instance.createBean(_path).instance(); 32 | } 33 | } 34 | 35 | ProxyFactory.prototype = { 36 | constructor: ProxyFactory, 37 | /** 38 | * get bean by name user customize or absolute file path 39 | * @param {[String]} name [name user customize or absolute file path] 40 | * @return {[Promise]} 41 | */ 42 | getBean: function(name) { 43 | return { 44 | then: function(callbck) { 45 | var proxy = _beans[name]; 46 | if (proxy && !proxy.isPromise) { 47 | callbck(proxy); 48 | return; 49 | } 50 | //wait for module load 51 | if (!proxy) { 52 | proxy = _beans[name] = { 53 | isPromise: true 54 | }; 55 | } 56 | _push.call(proxy, { 57 | resolve: callbck 58 | }); 59 | } 60 | }; 61 | }, 62 | /** 63 | * create bean 64 | * @param {[String]} name [alias name of module user customize] 65 | * @param {[String]} path [absolute path of module, required] 66 | * @return {[Proxy|Object]} 67 | */ 68 | createBean: function(name, path) { 69 | if (!path) { 70 | path = name; 71 | name = null; 72 | } 73 | var proxy = _beans[path]; 74 | if (!proxy || proxy.isPromise) { 75 | proxy = new Proxy(path, _loader); 76 | } 77 | name && resolveDependency(name); 78 | resolveDependency(path); 79 | resolveDependency(path.replace(EXTNAME_REG, '')); 80 | return proxy; 81 | 82 | //trigger the module dependencies to call 83 | function resolveDependency(key) { 84 | var promises = _beans[key]; 85 | 86 | if (promises && promises.isPromise) { 87 | _.forEach(promises, function(promise) { 88 | promise.resolve(proxy); 89 | }); 90 | } 91 | return _beans[key] = proxy; 92 | } 93 | }, 94 | /** 95 | * deal the path to absolute path relatvie to project 96 | * @param {[type]} path [description] 97 | * @param {[String]} hostPath [the module annotation exist] 98 | * @return {[type]} [description] 99 | */ 100 | dealBeanName: function(path, hostPath) { 101 | if (ABSOLUTE_PROJECT.test(path)) { 102 | return Path.join(_cwd, path); 103 | } 104 | if (RELATIVE_CUR.test(path) && hostPath) { 105 | return Path.join(hostPath, path); 106 | } 107 | return path; 108 | }, 109 | /** 110 | * whether or not the bean exist 111 | * @param {[String]} name [alias name of module user customize] 112 | * @param {[String]} path [path of module relatvie to project root] 113 | * @return {[Boolean]} [exist: true, not: false] 114 | */ 115 | existBean: function(name, path) { 116 | var proxy = _beans[name] || _beans[path]; 117 | if (proxy && typeof proxy.length !== 'undefined') { 118 | return true; 119 | } 120 | return false; 121 | }, 122 | /** 123 | * add express to filter all modules whether or not would be a proxy module 124 | * @param {[String]} regular [regular expression to match path and methods of module] 125 | * @param {[Array<{advice, method, callback}>]} watchers 126 | * @return {[void]} 127 | */ 128 | addExpress: function(regular, watchers) { 129 | _expresses.push(Express(regular, watchers)); 130 | }, 131 | /** 132 | * whether or not the express match the path 133 | * @param {[String]} path [description] 134 | * @return {[Boolean]} [description] 135 | */ 136 | match: function(path) { 137 | var proxy = false; 138 | _.forEach(_expresses, function(express) { 139 | if (express.match(path)) { 140 | _instance.getBean(path).then(function(proxy) { 141 | express.watch(proxy); 142 | }); 143 | proxy = true; 144 | } 145 | }); 146 | return proxy; 147 | } 148 | }; 149 | 150 | module.exports = new ProxyFactory; 151 | -------------------------------------------------------------------------------- /lib/annotation/Model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User: xin.lin 3 | * Date: 15-4-10 4 | * store the annotation data 5 | */ 6 | 'use strict'; 7 | var Path = require('path'); 8 | 9 | var ApplicationContext = require('../ApplicationContext'); 10 | 11 | var STR_EXP = /^(["'])(.+)\1$/, //string must in ".." 12 | JSON_EXP = /^[\[{](.+)[}\]]$/, //json must in {} or [] 13 | JSONFORMATTER_EXP = /([{,]\s*)(\w+)(\s*:)/g, //fetch key 14 | PARAM_EXP = /\s*([^,]+)\s*/g; //parameter is (param1, param2) 15 | 16 | module.exports = require("../base/Collection")(true, true).extend({ 17 | /** 18 | * Overwrite the default constructor 19 | * @param {[String]} name [annotation name] 20 | * @param {[String]} po [parameter] 21 | * @param {[String]} vo [variable name, maybe it is a function name, or a variable name] 22 | * @param {[String]} voParam [if variable is a function ,this is parameters of the function] 23 | * @param { [String]} classpath [file path] 24 | * @return {[type]} [description] 25 | */ 26 | constructor: function(name, po, vo, voParam, classpath) { 27 | this.data = { 28 | name: name, 29 | po: this.dealStr(po) || this.dealJson(po), 30 | vo: vo, 31 | voParam: voParam, 32 | classpath: classpath 33 | }; 34 | var VOPARAM_NAME = 'voParam'; 35 | //generate the quick getter and setter 36 | for (var attr in this.data) { 37 | if (attr == VOPARAM_NAME) { 38 | this[VOPARAM_NAME] = function(value) { 39 | if (!value) { 40 | return this.data[VOPARAM_NAME]; 41 | } 42 | this.data[VOPARAM_NAME] = this.dealVoParam(value); 43 | } 44 | continue; 45 | } 46 | this[attr] = function(attr) { 47 | return function(value) { 48 | return attribute.call(this, attr, value); 49 | } 50 | }(attr); 51 | } 52 | }, 53 | /** 54 | * deal the string value 55 | * @param {[String]} po [parameter for annotation] 56 | * @return {[String]} [parameter value] 57 | */ 58 | dealStr: function(po) { 59 | if (STR_EXP.test(po)) { 60 | po = po.substring(1, po.length - 1); 61 | return po; 62 | } 63 | }, 64 | /** 65 | * deal the json value 66 | * @param {[String]} po [parameter for annotation] 67 | * @return {[JSON]} [parameter value] 68 | */ 69 | dealJson: function(po) { 70 | if (JSON_EXP.test(po)) { 71 | po = JSON.parse(po.replace(JSONFORMATTER_EXP, function($, $1, $2, $3) { 72 | return [$1, '"', $2, '"', $3].join(""); 73 | })); 74 | return po; 75 | } 76 | }, 77 | /** 78 | * deal the function parameters 79 | * @param {[String]} params [function parameter] 80 | * @return {[type]} [description] 81 | */ 82 | dealVoParam: function(params) { 83 | if (params && PARAM_EXP.test(params)) { 84 | var list = []; 85 | params.replace(PARAM_EXP, function($, $1) { 86 | list.push($1); 87 | }); 88 | return list; 89 | } 90 | return null; 91 | }, 92 | /** 93 | * return real module instance 94 | * @return {[type]} [description] 95 | */ 96 | instance: function() { 97 | return this.exports().instance(); 98 | }, 99 | /** 100 | * load the module proxy 101 | * @param {[boolean]} proxy [whether or not to delegate the module object] 102 | * @return {[Module]} [js file] 103 | */ 104 | exports: function(proxy, unuseAlias) { 105 | if (!this.data.exports) { 106 | var classpath = this.data.classpath, 107 | extname = Path.extname(classpath); 108 | if(proxy){ 109 | this.data.exports = require(classpath); 110 | }else{ 111 | if(unuseAlias){ 112 | this.data.exports = ApplicationContext.createBean(classpath); 113 | } else { 114 | this.data.exports = ApplicationContext.createBean(Path.basename(classpath, extname), classpath); 115 | } 116 | } 117 | } 118 | return this.data.exports; 119 | } 120 | }, { 121 | /** 122 | * create a new instance 123 | * @param {[String]} name [annotation name] 124 | * @param {[String]} po [parameter] 125 | * @param {[String]} vo [variable name, maybe it is a function name, or a variable name] 126 | * @param {[String]} voParam [if variable is a function ,this is parameters of the function] 127 | * @param { [String]} classpath [file path] 128 | * @return {[Model]} [model instance] 129 | */ 130 | create: function(name, po, vo, voParam, classpath) { 131 | return this.add(new this(name, po, vo, voParam, classpath)); 132 | } 133 | 134 | }); 135 | /** 136 | * if value is not exist, then return value, or set the attribute value 137 | * @param {[String]} name [attribute name] 138 | * @param {[String]} value [attribute value] 139 | * @return {[Object]} [any value] 140 | */ 141 | function attribute(name, value) { 142 | if (value) { 143 | return this.data[name] = value; 144 | } 145 | return this.data[name]; 146 | } 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-annotation 2 | node-annotation开发的初衷是因为node本身在服务层面并不太成熟,而且代码层次及维护各大公司都有自己的做法,借鉴于Java里spring的想法开发了这个,当然在功能实现上和spring相差甚多,这里只实现了几个重要功能, 欢迎大家提供意见建议。(QQ群:523291195) 3 | 4 | ## 注解 5 | 注解区别于正常可运行的代码,仅仅是注释的写法,这里写法格式为: 6 | 7 | `/*@注解名称(注解参数)*/`注解参数支持格式: 8 | - 单引号或者双引号括起来的字符串 9 | - JSON格式 10 | 11 | ## 如何使用 12 | **纳入node-annotation管理** 如果需要使用node-annotation来处理模块,需要在扫描文件完成后才能做业务操作 13 | 14 | ```javascript 15 | var nodeAnnotation = require('node-annotation'); 16 | /** 配置全局错误处理 17 | * [Function] 错误处理函数 18 | */ 19 | nodeAnnotation.setGlobalErrorHandler(function(err){ 20 | console.error(err); 21 | }) 22 | /** 配置node-annotation内的日志流出 23 | * [Boolean] 是否开启日志,默认true 24 | * [String] "error/warn/info/log" 输出日至级别,默认warn 25 | * [Function/LoggerObject] 日志处理函数或对象(类似log4js的Logger对象),默认为console 26 | */ 27 | nodeAnnotation.setLogger(true, 'info', function(str, level) { 28 | console.log('[NodeAnnotation]', str); 29 | }); 30 | /* 配置资源路径 31 | * [String] 配置文件所在路径(文件夹),默认工程目录下的resoucres文件夹 32 | */ 33 | nodeAnnotation.configurePath(path.join(process.cwd(), 'resources')); 34 | nodeAnnotation.start(源文件路径, function() { 35 | nodeAnnotation.app(/*你的express app*/) 36 | //你的业务代码 37 | }); 38 | ``` 39 | 40 | **限制** 使用该注解功能有个约束,如果注解修饰的是方法,只针对通过`module.exports={..}`导出的才有效,比如正确的写法
41 | 42 | ```javascript 43 | /*@Controller*/ 44 | module.exports = { 45 | /*@RequestMapping("/user/{name}")*/ 46 | user: function(name, req, res){ 47 | //do something 48 | } 49 | }; 50 | ``` 51 | 52 | 错误的写法
53 | 54 | ```javascript 55 | /*@Controller*/ 56 | /*@RequestMapping("/user/{name}")*/ 57 | function user(name, req, res){ 58 | //do something 59 | } 60 | module.exports = { 61 | user: user 62 | }; 63 | ``` 64 | 65 | ## 启动配置 66 | 67 | - 全局错误配置 68 | 69 | > 会包裹`start`的回调函数(你的业务代码应该全部置于回调中),能够捕获其内的同步或者异步错误(捕获参见[trycatch](https://www.npmjs.com/package/trycatch)) 70 | > 71 | > 默认情况会直接打印错误到控制台 72 | 73 | ```javascript 74 | /** 配置全局错误处理 75 | * [Function] 错误处理函数 76 | */ 77 | nodeAnnotation.setGlobalErrorHandler(function(err){ 78 | console.error(err); 79 | }) 80 | ``` 81 | 82 | - 日志流出配置 83 | 84 | > node-annotation在编译和执行过程中会产生一系列日志,在不同情况下你可能对是否输出日志以及需要哪些日志有着不同的需求。我们定义了四个日志级别:error,warn,info,log。 85 | > 86 | > 默认情况下我们会打印warn级别及以上的日志到console,当你开启debug模式(指直接在启动node时追加`--debug`或`--debug-brk`)时,则会打印log级别的日志(即全部日志)。 87 | 88 | ```javascript 89 | /** 配置node-annotation内的日志流出 90 | * [Boolean] 是否开启日志,默认true 91 | * [String] "error/warn/info/log" 输出日至级别,默认warn 92 | * [Function/LoggerObject] 日志处理函数或对象(类似log4js的Logger对象),默认为console 93 | */ 94 | nodeAnnotation.setLogger(true, 'info', function(str, level) { 95 | console.log('[NodeAnnotation]', str); 96 | }); 97 | ``` 98 | 99 | - 资源文件路径配置 100 | 101 | > 在注解中使用[Configure](./doc/annotation/Configure.md)时需要配置资源文件所在路径 102 | > 103 | > 默认情况会使用node启动目录下的resources目录 104 | 105 | ```javascript 106 | /* 配置资源路径 107 | * [String] 配置文件所在路径(文件夹),默认工程目录下的resoucres文件夹 108 | */ 109 | nodeAnnotation.configurePath(path.join(process.cwd(), 'resources')); 110 | ``` 111 | 112 | - 路由中间件启用 113 | 114 | > 在注解中使用[RequestMapping](./doc/annotation/Controller.md)时需要配置web服务,我们会将你的路由规则作为一个router中间件置入外围的express服务。 115 | > 116 | > 请务必在`start`的回调函数内配置该中间件。 117 | 118 | ```javascript 119 | var app = express.createServer(); 120 | nodeAnnotation.app(app); 121 | // 实际在annotation内会 app.use(`AnnotationRouter`) 122 | ``` 123 | 124 | - 编译启动 125 | 126 | > 注解需要指定它需要编译的目录,将扫描该目录下所有文件进行正则匹配完成注解编译。 127 | > 128 | > 请注意一般不要把node_modules包含进扫描目录,这样会极大降低启动速度,尽量仅包含使用了注解的少数几个目录。 129 | 130 | ```javascript 131 | /** 注解编译启动 132 | * [Array] 需要被扫描编译的目录 133 | * [Function] 扫描完成的回调,你的全部业务代码应该置于这里 134 | */ 135 | nodeAnnotation.start(dirs, function() { 136 | nodeAnnotation.app(/*你的express app*/) 137 | //你的业务代码 138 | }); 139 | ``` 140 | 141 | ## 注解 142 | 143 | ### 分层注解 144 | - [Controller](./doc/annotation/Controller.md) 访问控制层,请求映射后的处理类 145 | - [RequestMapping](./doc/annotation/Controller.md) 请求映射 146 | - [ResponseBody](./doc/annotation/Controller.md) 返回包装 147 | - [ExceptionHandler](./doc/annotation/Controller.md) 异常处理 148 | 149 | - [ControllerAdvice](./doc/annotation/Controller.md) 访问器增强,将其内的[ExceptionHandler](./doc/annotation/Controller.md)应用到所有Controller 150 | - [Service](./doc/annotation/Controller.md) 业务层,负责处理业务相关逻辑 151 | - [Repository](./doc/annotation/Controller.md) 持久层,负责数据持久化相关 152 | 153 | ### 组件依赖注解 154 | - [Autowired](./doc/annotation/DI.md) 能够将组件自动注入到模块中 155 | - [Component](./doc/annotation/DI.md) 识别为组件,会被存储到组件池中以便注入时使用 156 | 157 | ### 切面注解 158 | - [Aspect](./doc/annotation/AOP.md) 切面标识 159 | - [PointCut](./doc/annotation/AOP.md) 切点,描述哪些模块方法会被处理 160 | - [Before](./doc/annotation/AOP.md) 符合切点描述的方法执行前处理 161 | - [After](./doc/annotation/AOP.md) 符合切点描述的方法执行后处理 162 | - [Around](./doc/annotation/AOP.md) 符合切点描述的方法执行前后都会被处理 163 | - [Throws](./doc/annotation/AOP.md) 符合切点描述的方法抛出异常时处理 164 | - [AfterReturning](./doc/annotation/AOP.md) 符合切点描述的方法的回调执行前处理 165 | 166 | ### 异步注解(类似ES7async/await) 167 | - [Async](./doc/annotation/fiber.md) 注解一个异步函数,该函数内可以同步调用其他Async函数 168 | - [AsyncWrap](./doc/annotation/fiber.md) 将一个callback形式的函数库包装为Async形式 169 | 170 | ### 自动加载注解 171 | - [AutoLoad](./doc/annotation/AutoLoad.md) 自动加载文件,一般在创建注解类的时候需要用到 172 | 173 | ### 配置加载 174 | - [Configure](./doc/annotation/Configure.md) 加载配置文件,通过该标签直接获取配置里的值 175 | 176 | ## 自定义注解 177 | 请挪驾 [自定义注解](./doc/Annotation.md) 178 | 179 | ## 注解数据模型 180 | [注解数据模型](./doc/Model.md) 181 | 182 | ## 使用demo及test 183 | 请参考项目 [node-annotation-example](https://github.com/Robinlim/node-annotation-example) 184 | 185 | ## 注解第三方扩展 186 | 请参考项目 [node-annotation-extend](https://github.com/Robinlim/node-annotation-extend) 187 | -------------------------------------------------------------------------------- /lib/annotation/controller/Controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: xin.lin 3 | * @Date: 15-4-10 4 | * control the request and response 5 | */ 6 | /*@AutoLoad*/ 7 | var _ = require('lodash'), 8 | Logger = require('../../Logger'), 9 | trycatch = require('trycatch'), 10 | ApplicationContext = require("../../ApplicationContext"); 11 | 12 | var UNCERTAIN_METHOD = 'all'; 13 | 14 | var Controller = module.exports = require("../Annotation").extend({ 15 | /** 16 | * find the controller of corresponding url 17 | * @return {[type]} [description] 18 | */ 19 | execute: function() { 20 | //load bean proxy by annotation parameter 21 | var po = this.model.po(); 22 | ApplicationContext.createBean(po, this.model.classpath()); 23 | 24 | this.exceptionHandlers = []; 25 | this.traverse(function(i, item){ 26 | var name = item.model.name(); 27 | switch(name){ 28 | case 'ExceptionHandler': 29 | this.exceptionHandlers.push(item.data); 30 | break; 31 | case 'RequestMapping': 32 | this.makeRequestMapping(item); 33 | break; 34 | default: 35 | // do nothing 36 | break; 37 | } 38 | }, this); 39 | }, 40 | makeRequestMapping: function(item){ 41 | var _this = this; 42 | matcher = _.bind(item.match, item), 43 | handler = function(req, res, matches){ 44 | trycatch(function(){ 45 | item.run.call(item, req, res, matches); 46 | }, function(err){ 47 | _this.handlerException(err, req, res); 48 | }); 49 | }; 50 | _.forEach(item.data, function(el){ 51 | if(!el.isExp){ 52 | var method = el.method?el.method.toLowerCase():UNCERTAIN_METHOD; 53 | if(!Controller.staticMap[el.url]){ 54 | Controller.staticMap[el.url] = {}; 55 | } 56 | Controller.staticMap[el.url][method] = handler; 57 | } else if(el.prefix){ 58 | if(!Controller.prefixMap[el.prefix]){ 59 | Controller.prefixMap[el.prefix] = []; 60 | } 61 | Controller.prefixMap[el.prefix].push({ 62 | handler: handler, 63 | matcher: matcher 64 | }); 65 | } else { 66 | Controller.regArray.push({ 67 | handler: handler, 68 | matcher: matcher 69 | }); 70 | } 71 | }); 72 | }, 73 | handlerException: function(err, req, res){ 74 | var breakNext = false; 75 | _.forEach(this.exceptionHandlers, function(el){ 76 | if(breakNext = el.match(err)){ 77 | el.fun(err, req, res); 78 | return false; 79 | } 80 | }); 81 | return breakNext; 82 | } 83 | }, { 84 | //annotation name 85 | name: "Controller", 86 | staticMap: {}, 87 | prefixMap: {}, 88 | regArray: [], 89 | init: function(app){ 90 | Logger.info('全部路由项:') 91 | Logger.info('staticMap',Controller.staticMap); 92 | Logger.info('prefixMap',Controller.prefixMap); 93 | Logger.info('regArray',Controller.regArray); 94 | app.use(function(req, res, next){ 95 | Controller.route(req, res, next); 96 | }); 97 | }, 98 | route: function(req, res, next) { 99 | var path = req.path, 100 | params, 101 | index = path.indexOf("?"), 102 | url = req.path.substring(0, index == -1 ? path.length : index), 103 | notfound = true, 104 | prefix,matches; 105 | if(_.has(Controller.staticMap, path)){ 106 | Logger.log('[Router] staticMap in') 107 | var it = Controller.staticMap[path], 108 | method = req.method.toLowerCase(); 109 | if(_.has(it, method)){ 110 | it[method](req, res); 111 | notfound = false; 112 | } else if(_.has(it, UNCERTAIN_METHOD)){ 113 | it[UNCERTAIN_METHOD](req, res); 114 | notfound = false; 115 | } 116 | } 117 | if(notfound && (prefix = path.split('/')[1]) 118 | && _.has(Controller.prefixMap, prefix)){ 119 | Logger.log('[Router] prefixMap in') 120 | // '/'.length + prefix.length + '/'.length 121 | var postfix = path.slice(prefix.length+2); 122 | _.forEach(Controller.prefixMap[prefix], function(el){ 123 | if(matches = el.matcher(postfix, req, res)){ 124 | el.handler(req, res, matches); 125 | notfound = false; 126 | return false; 127 | } 128 | }); 129 | } 130 | if(notfound){ 131 | Logger.log('[Router] regArray in') 132 | _.forEach(Controller.regArray, function(el){ 133 | if(matches = el.matcher(path, req, res)){ 134 | el.handler(req, res, matches); 135 | notfound = false; 136 | return false; 137 | } 138 | }); 139 | } 140 | if (notfound) { 141 | if(_.has(Controller.staticMap, '/404')){ 142 | var it = Controller.staticMap['/404'], 143 | method = req.method.toLowerCase(); 144 | if(_.has(it, method)){ 145 | it[method](req, res); 146 | } else if(_.has(it, UNCERTAIN_METHOD)){ 147 | it[UNCERTAIN_METHOD](req, res); 148 | } else { 149 | res.status(404).end(path + " not found!!"); 150 | } 151 | } else { 152 | res.status(404).end(path + " not found!!"); 153 | } 154 | } 155 | } 156 | }); 157 | 158 | ApplicationContext.app().then(function(app) { 159 | Controller.init(app); 160 | }); 161 | -------------------------------------------------------------------------------- /lib/annotation/controller/RequestMapping.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: xin.lin 3 | * @Date: 15-4-10 4 | * request mapping, supports the formatter as below 5 | * @RequestMapping("/goods") 6 | * @RequestMapping("/ajax/goods/{name}") 7 | * @RequestMapping(["/goods", "/ajax/goods/{name}"]) 8 | * @RequestMapping({ url : "/goods",method : "post"}) 9 | */ 10 | /*@AutoLoad*/ 11 | var _ = require('lodash'), 12 | domain = require('domain'), 13 | utils = require("../../Utils"); 14 | 15 | var URLPATTER = /\{([^}]*)\}/g, //url format like /pathname/{name} 16 | CUSTOME_EXP = /\{/, 17 | PRE_EXP = "^", 18 | END_EXP = "/?$"; 19 | var RequestMapping = module.exports = require("./Controller").extend({ 20 | /** 21 | * find the controller of corresponding url 22 | * @return {[type]} [description] 23 | */ 24 | execute: function() { 25 | //do nothing 26 | }, 27 | /** 28 | * compile the model 29 | * @param {[Model]} model [annotation data] 30 | * @return {[type]} [description] 31 | */ 32 | compile: function(model) { 33 | var po = model.po(), 34 | type = utils.typeofObject(po).value; 35 | this.cpMethod(model); 36 | switch (type) { 37 | case "[object String]": 38 | return [this.cpUrl(po)]; 39 | case "[object Array]": 40 | return this.cpUrls(po); 41 | case "[object Object]": 42 | return [this.cpReq(po)]; 43 | default: 44 | break; 45 | } 46 | return; 47 | }, 48 | /** 49 | * match the request and export params 50 | * @param {[String]} path [url path] 51 | * @param {[Request]} req [request object] 52 | * @param {[Response]} res [response object] 53 | * @return {[Array|Boolean]} 54 | */ 55 | match: function(path, req, res) { 56 | var match,found = false; 57 | this.data.some(function(item, i) { 58 | if ((match = path.match(item.url)) && this.check(req, item)) { 59 | found = true; 60 | return true; 61 | } 62 | }, this); 63 | if(found){ 64 | return match; 65 | } else { 66 | return false; 67 | } 68 | }, 69 | makeparam: function(req, res, matches){ 70 | var args, 71 | params, 72 | funcParams = this.params || []; 73 | params = this.mix(funcParams, matches.slice(1)); 74 | params.req = params.request = req; 75 | params.res = params.response = res; 76 | params.reqData = req.method.toLowerCase() == 'get' ? req.query : req.body; 77 | args = []; 78 | _.forEach(funcParams, function(v, i) { 79 | args.push(params[v]); 80 | }); 81 | return args; 82 | }, 83 | /** 84 | * check the request has right headers 85 | * @param {[type]} req [description] 86 | * @param {[type]} item [description] 87 | * @return {[type]} [description] 88 | */ 89 | check: function(req, item) { 90 | if (item.method && item.method.toLowerCase() != req.method.toLowerCase()) { 91 | return false; 92 | } 93 | return true; 94 | }, 95 | /** 96 | * execute the module 97 | * @return {[type]} [description] 98 | */ 99 | run: function(req, res, matches) { 100 | var model = this.model, 101 | instance = model.instance(); 102 | instance[model.vo()].apply(instance, this.makeparam(req, res, matches || [])); 103 | }, 104 | /** 105 | * compile the method 106 | * @param {[type]} model [description] 107 | * @return {[type]} [description] 108 | */ 109 | cpMethod: function(model) { 110 | this.params = model.voParam(); 111 | }, 112 | /** 113 | * deal the patter as bellow 114 | * "/goods" 115 | * "/goods/{user}" 116 | * "/goods/{good:00[1~9]*ab}" 117 | * @param {[String]} url [description] 118 | * @param {[Boolean]} useExp [if url is a RegExp] 119 | * @return {[Object]} [description] 120 | */ 121 | cpUrl: function(url, useExp) { 122 | if(useExp){ 123 | return { 124 | isExp: true, 125 | url: RegExp(url) 126 | } 127 | } 128 | if (CUSTOME_EXP.test(url)) { 129 | var data = { 130 | params: [], 131 | isExp: true 132 | }; 133 | // deal "/prefix/..." and store prefix to data.prefix 134 | var piece = url.split('/'),prefix; 135 | if((prefix = piece.shift()) == ""){ 136 | prefix = piece.shift(); 137 | } 138 | if(!URLPATTER.test(prefix)){ 139 | data.prefix = prefix; 140 | url = piece.join('/'); 141 | } 142 | 143 | data.url = RegExp(PRE_EXP + url.replace(URLPATTER, function($, $1) { 144 | var pos = $1.indexOf('='); 145 | if(pos > 0){ 146 | var varName = $1.slice(0,pos), 147 | regStr = $1.slice(pos+1); 148 | data.params.push(varName); 149 | return "(" + regStr + ")"; 150 | } else { 151 | data.params.push($1); 152 | return "([^/?]+)"; 153 | } 154 | }) + END_EXP); 155 | return data; 156 | } 157 | return { 158 | url: url 159 | }; 160 | }, 161 | /** 162 | * deal the patter as bellow 163 | * ["/goods", "/goods/{user}"] or [{url: "/goods"}] 164 | * @param {[type]} po [description] 165 | * @return {[type]} [description] 166 | */ 167 | cpUrls: function(po) { 168 | var urls = []; 169 | _.forEach(po, function(v, i) { 170 | urls.push(utils.typeofObject(v).isObject() ? 171 | this.cpReq(v) : 172 | this.cpUrl(v)); 173 | }, this); 174 | return urls; 175 | }, 176 | /** 177 | * deal the patter as bellow 178 | * {"url": "/goods", "produces":"text/javascript;charset=UTF-8"} 179 | * @param {[type]} po [description] 180 | * @return {[type]} [description] 181 | */ 182 | cpReq: function(po) { 183 | return utils.extend(po, this.cpUrl(po.url, po.useExp)); 184 | }, 185 | /** 186 | * with two array to generate a map 187 | * @param {[Array]} arryKeys [keys] 188 | * @param {[Array]} arryValues [values] 189 | * @return {[type]} [description] 190 | */ 191 | mix: function(arryKeys, arryValues) { 192 | if (!arryKeys || !arryValues) return {}; 193 | var result = {}; 194 | _.forEach(arryKeys, function(v, i) { 195 | result[v] = arryValues[i]; 196 | }); 197 | return result; 198 | } 199 | }, { 200 | //annotation name 201 | name: "RequestMapping" 202 | }); 203 | -------------------------------------------------------------------------------- /lib/annotation/Annotation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: xin.lin 3 | * @Date: 15-4-10 4 | * annotation base, all others will extend here 5 | * if you want to use the annotation to control your code, you must obey code format as below: 6 | * \/*@Controller*\/ 7 | * \/@RequestMapping("/")\/ 8 | * module.exports = { 9 | * \/* @RequestMapping("/users")\/ 10 | * list: function(request, response){}, 11 | * \/* @RequestMapping("/users/{name}")\/ 12 | * user: function(name, request, response){} 13 | * } 14 | * 15 | * three steps to follow: 16 | * 1. recognize the annotation 17 | * 2. store the relative data for annotation 18 | * 3. execute the annotation 19 | * 20 | */ 21 | var _ = require('lodash'), 22 | Class = require("../base/Class"); 23 | 24 | var Annotation = module.exports = require("../base/Collection")(true, true).extend( 25 | //instance method 26 | { 27 | /** 28 | * Overwrite the default constructor 29 | * @param {[Model]} model [annotation data] 30 | * @return {[type]} [description] 31 | */ 32 | constructor: function(model) { 33 | this.data = this.compile(model); 34 | this.model = model; 35 | }, 36 | /** 37 | * the annotation affect 38 | * allow no return or return a Promise 39 | * @return {[type]} [nil|Promise] 40 | */ 41 | execute: function() { 42 | //do something by children 43 | }, 44 | /** 45 | * the annotation affect 46 | * traverse apply execute to each annotation instance 47 | * @return {[type]} [description] 48 | */ 49 | affectAnnotation: function() { 50 | var bulk = []; 51 | this.traverse(function(i, instance) { 52 | var res = instance.affectAnnotation(); 53 | if(isPromise(res)){ 54 | bulk.push(res); 55 | } 56 | }, this); 57 | var cur = this.execute(); 58 | return isPromise(cur)? 59 | cur.then(function(){ 60 | return Promise.all(bulk); 61 | }) : Promise.all(bulk); 62 | }, 63 | /** 64 | * compile the model 65 | * @param {[Model]} model [annotation data] 66 | * @return {[type]} [description] 67 | */ 68 | compile: function(model) { 69 | return model; 70 | } 71 | }, 72 | //static method 73 | { 74 | //annotation name 75 | // Function.prototype['name'] is anti-writable , use aname instead 76 | aname: "", 77 | /** 78 | * parse the annotation content 79 | * @param {[Iterator[Model]]} ms [iterator of model data] 80 | * @param {[Model]]} model [annotation data] 81 | * @param {[Array]} annotations [description] 82 | * @return {[type]} [description] 83 | */ 84 | parse: function(ms, model, annotations) { 85 | if (!ms.hasNext() && !model) return; 86 | var annotation; 87 | if (!model) { 88 | model = ms.next(); 89 | } 90 | if (!annotations) { 91 | annotations = []; 92 | } 93 | //create root node 94 | if (this == Annotation) { 95 | this.isEmpty() && this.create(); 96 | } 97 | annotation = this.annotations(model.name()); 98 | 99 | if (annotation) { 100 | //create annotation instance tree 101 | annotations.unshift(annotation); 102 | this.current().add(annotation.create(model)); 103 | return annotation.parse(ms, null, annotations); 104 | } 105 | 106 | if (this == Annotation) { 107 | _.forEach(annotations, function(item) { 108 | if (annotation = item.annotations(model.name())) { 109 | annotation.parse(ms, model, annotations); 110 | return false; 111 | } 112 | }); 113 | if (!annotation) { 114 | this.parse(ms, ms.next(), annotations); 115 | } 116 | return; 117 | } 118 | // 使用 parent() 替代了__super__.constructor 119 | // return this.__super__.constructor.parse(ms, model, annotations); 120 | return this.parent().parse(ms, model, annotations); 121 | }, 122 | /** 123 | * get annotation instance 124 | * @param {[Model]} model [annotation data] 125 | * @return {[type]} [description] 126 | */ 127 | create: function(model) { 128 | return this.add(new this(model)); 129 | }, 130 | /** 131 | * get or set _parent(means its parent class by extend) of current annotaionClass 132 | * @param {[type]} parent [description] 133 | * @return {[type]} [description] 134 | */ 135 | parent: function(parent){ 136 | if(parent){ 137 | this._parent = parent; 138 | } 139 | return this._parent; 140 | }, 141 | /** 142 | * subclass will be added here 143 | * @param {[type]} protoProps [description] 144 | * @param {[type]} staticProps [description] 145 | * @return {[type]} [description] 146 | */ 147 | extend: function(protoProps, staticProps) { 148 | // allow use 'name' to define 'aname' 149 | staticProps.aname = staticProps.aname || staticProps.name; 150 | var AnnotationClass = Class.extend.apply(this, arguments); 151 | //静态方法多重继承不适合集合方式存储 152 | AnnotationClass._annotations = {}; 153 | AnnotationClass._children = []; 154 | //追加_parent方便子类实例查找父类 155 | AnnotationClass._parent = this; 156 | return this.addAnnotation(staticProps.name, AnnotationClass); 157 | }, 158 | /** 159 | * add annotation 160 | * @param {[type]} annotation [description] 161 | */ 162 | addAnnotation: function(name, annotation) { 163 | return this.annotations()[name] = annotation; 164 | }, 165 | /** 166 | * fetch the annotation by name or all annotations if name not exist 167 | * @return {[Annotation]} [description] 168 | */ 169 | annotations: function(key) { 170 | if (!this._annotations) { 171 | this._annotations = {}; 172 | } 173 | return key ? this._annotations[key] : this._annotations; 174 | }, 175 | /** 176 | * traverse all annotations 177 | * @param {Function} fn [description] 178 | * @param {[type]} context [description] 179 | * @return {[type]} [description] 180 | */ 181 | traverseAnnotations: function(fn, context) { 182 | var annotations = this.annotations(); 183 | for (var attr in annotations) { 184 | if (fn.call(context || this, attr, annotations[attr]) === false) break; 185 | } 186 | } 187 | }); 188 | 189 | function isPromise(p){ 190 | // return p && (p instance of Promise) 191 | // 使用其他promise库构造的promise对象不能如上判断,改为判断then方法 192 | return p && typeof p.then == 'function'; 193 | } 194 | -------------------------------------------------------------------------------- /lib/Scanner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: xin.lin 3 | * @Date: 15-4-1 4 | * scan the annotation 5 | * 注解间有父子关系,子的识别必须依赖父,所以每个模块父注解必须存在于子注解之前 6 | * if you want to use the annotation to control your code, you must obey code format as below: 7 | * \/*@Controller*\/ 8 | * \/@RequestMapping("/")\/ 9 | * module.exports = { 10 | * \/* @RequestMapping("/users")\/ 11 | * list: function(request, response){}, 12 | * \/* @RequestMapping("/users/{name}")\/ 13 | * user: function(name, request, response){} 14 | * } 15 | */ 16 | var Path = require("path"), 17 | trycatch = require("trycatch"), 18 | _ = require('lodash'); 19 | 20 | var _Iterator = require("./base/_Iterator"), 21 | Model = require("./annotation/Model"), 22 | Annotation = require("./annotation/Annotation"), 23 | ApplicationContext = require("./ApplicationContext"), 24 | Utils = require("./Utils"), 25 | Logger = require('./Logger'); 26 | 27 | var START_REG = /\/\*/m, //all annotation is start with /* 28 | ANNOTATION_REG = /^\s*\/\*@(\w+)\s*(?:\(([^)]+)\))?\s*\*\//m, //annotation format is /*@AnnotationName(parameters)*/ 29 | VARIABLE_REG = /^\s*(\w+)\s*:\s*(?:function\s*\(([^)]*)\)|[^,]{1})/m; //variable format is variable:value or variable: function(parameters) 30 | 31 | var extnames = { 32 | ".js": 1, 33 | ".coffee": 1 34 | }, 35 | fileCache = {}; 36 | 37 | require("./annotation/load/AutoLoad"); 38 | 39 | /** 40 | * scan js file in the path 41 | * @param {[String|Array]} path [scan path] 42 | * @param {[JSON]} ignoreDirs [ignore directories] 43 | */ 44 | function Scanner(path, ignoreDirs) { 45 | this.path = typeof path == 'string' ? [path] : path; 46 | this.ignoreDirs = ignoreDirs; 47 | } 48 | 49 | Scanner.prototype = { 50 | constructor: Scanner, 51 | /** 52 | * execute scan the directory 53 | * @param {[Function]} end [all files in directory have been scanned, this function will be called] 54 | * @return {[void]} 55 | */ 56 | execute: function(end) { 57 | var dirs = this.path; 58 | //add node-annotation library 59 | dirs.unshift(__dirname); 60 | 61 | var cache = [], 62 | bulks = [], 63 | _this = this; 64 | 65 | Logger.info('将编译以下目录:\n',dirs) 66 | _.forEach(dirs, _.bind(function(path, i) { 67 | bulks.push( 68 | Utils.recursiveDirs(path, function(content, filename) { 69 | if (fileCache[filename]) return; 70 | if (!extnames[Path.extname(filename)]) return; 71 | fileCache[filename] = 1; 72 | // 识别分析注解内容 73 | var ms = new Analyse(new ContentIterator(content), filename).execute(); 74 | if (!ms.hasNext()) return; 75 | // 优先加载AutoLoad注解包裹的对象,一般会是注解类 76 | if (ms.index(0).name() == "AutoLoad") { 77 | // AutoLoad必须是该模块的第一个注解,如果有其他注解会被缓存起来,在下一个阶段执行 78 | var msFirst = [ms.next()]; 79 | Annotation.parse(new _Iterator(msFirst)); 80 | } 81 | // AutoLoad模块中除AutoLoad注解本身外的注解以及其他所有模块的注解暂时缓存 82 | if (!ms.hasNext()) return; 83 | cache.push({ 84 | filename: filename, 85 | ms: ms 86 | }); 87 | }, _this.ignoreDirs) 88 | ); 89 | }, _this)); 90 | 91 | // 会所有文件分析完后执行 92 | Promise.all(bulks).then(function(){ 93 | // 将AutoLoad描述的对象优先生效,一般会是注解类 94 | return Annotation.current().affectAnnotation(); 95 | }).then(function(){ 96 | // 解析被注解类描述的对象 97 | _.forEach(cache, function(el, i) { 98 | Annotation.parse(el.ms); 99 | Logger.info(el.filename); 100 | }); 101 | // 所有注解对象生效 102 | return Annotation.current().affectAnnotation(); 103 | }).then(function(){ 104 | // 业务代码启动,会包裹全局错误(指业务代码) 105 | end && trycatch(end, ApplicationContext.getGlobalErrorHandler()); 106 | }).catch(function(err){ 107 | Logger.error(err, err.stack) 108 | }) 109 | } 110 | }; 111 | 112 | module.exports = Scanner; 113 | 114 | /** 115 | * extract the annotation from file content 116 | * @param {[ContentIterator]} iterator [iterator the file content] 117 | * @param {[String]} filename [file name] 118 | */ 119 | function Analyse(iterator, filename) { 120 | this.iterator = iterator; 121 | this.filename = filename; 122 | } 123 | 124 | Analyse.prototype = { 125 | constructor: Analyse, 126 | execute: function() { 127 | var filename = this.filename, 128 | iterator = this.iterator, 129 | result, cache = []; 130 | //traverse the content to extract annotation information 131 | while (iterator.hasNext()) { 132 | result = iterator.next().match(START_REG); 133 | if (result) { 134 | iterator.go(iterator.index + result.index); 135 | result = analyseAnnotation(); 136 | if (result === false) break; 137 | cache = cache.concat(result); 138 | } else { 139 | break; 140 | } 141 | } 142 | return new _Iterator(cache); 143 | 144 | //analyse annotation entity 145 | function analyseAnnotation() { 146 | var cache = [], 147 | tmp; 148 | //for annotation 149 | result = iterator.next().match(ANNOTATION_REG); 150 | if (!result) return false; 151 | while (result) { 152 | cache.push(Model.create(result[1], result[2], null, null, filename)); 153 | iterator.add(result.index + result[0].length); 154 | //may one variable has multiple annotations 155 | result = iterator.next().match(ANNOTATION_REG) || { 156 | index: Infinity 157 | }; 158 | //for variable 159 | tmp = iterator.next().match(VARIABLE_REG) || { 160 | index: Infinity 161 | }; 162 | if (tmp.index < result.index) { 163 | result = tmp; 164 | iterator.add(result.index + result[0].length); 165 | _.forEach(cache, function(m, i) { 166 | //for variable name and parameters 167 | m.vo(result[1]); 168 | m.voParam(result[2]); 169 | }); 170 | break; 171 | } 172 | if (result.index == Infinity) break; 173 | } 174 | return cache; 175 | } 176 | } 177 | }; 178 | 179 | /** 180 | * iterator the content 181 | * @param {[String]} content [content of js file] 182 | */ 183 | function ContentIterator(content) { 184 | this.content = content; 185 | this.index = 0; 186 | } 187 | 188 | ContentIterator.prototype = { 189 | constructor: ContentIterator, 190 | next: function() { 191 | return this.content.substr(this.index); 192 | }, 193 | hasNext: function() { 194 | return this.index < this.content.length - 1; 195 | }, 196 | go: function(index) { 197 | if (index !== undefined) 198 | this.index = index; 199 | return this.index; 200 | }, 201 | add: function(count) { 202 | return this.index += count; 203 | }, 204 | subtract: function(count) { 205 | return this.index -= count; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /lib/proxy/Proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: robin 3 | * @Date: 2016-06-21 10:05:04 4 | * @Email: xin.lin@qunar.com 5 | * @Last modified by: robin 6 | * @Last modified time: 2016-06-23 14:41:02 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var Path = require('path'), 12 | trycatch = require('trycatch'), 13 | _ = require('lodash'), 14 | concat = ([]).concat, 15 | slice = ([]).slice; 16 | 17 | var INTERCEPTOR = require('../INTERCEPTOR'), 18 | PROXYCYCLE = require('../PROXYCYCLE'); 19 | /** 20 | * Proxy 21 | * @param {[String]} path [absolute path] 22 | * @param {[Function]} loader [module require] 23 | * @return {[Proxy]} 24 | */ 25 | module.exports = function(path, loader) { 26 | // load original module 27 | var _instance = loader.call(this, path); 28 | var _methods = {}; 29 | 30 | function Proxy() {} 31 | 32 | Proxy.prototype = { 33 | constructor: Proxy, 34 | /** 35 | * [function description] 36 | * @param {[type]} advice [description] 37 | * @param {[type]} method [description] 38 | * @param {Function} callback [description] 39 | * @return {[type]} [description] 40 | */ 41 | addInterceptor: function(advice, method, interceptor, callback) { 42 | if (typeof advice == 'function') { 43 | callback = advice; 44 | advice = PROXYCYCLE.BEFORE; 45 | interceptor = INTERCEPTOR.MODULE; 46 | } else if (typeof method == 'function') { 47 | callback = method; 48 | if (typeof advice != 'number') { 49 | method = advice; 50 | advice = PROXYCYCLE.BEFORE; 51 | } else { 52 | interceptor = INTERCEPTOR.MODULE; 53 | } 54 | } else if (typeof interceptor == 'function'){ 55 | callback = interceptor; 56 | interceptor = INTERCEPTOR.METHOD; 57 | } 58 | if (typeof(method) == 'undefined'){ 59 | interceptor = INTERCEPTOR.MODULE; 60 | } 61 | switch (interceptor) { 62 | //all methods of module 63 | case INTERCEPTOR.MODULE: 64 | _.mapKeys(_instance, function(value, key) { 65 | this.addMethodInterceptor(key, advice, INTERCEPTOR.METHOD, callback); 66 | }, this); 67 | break; 68 | //specific method of module 69 | case INTERCEPTOR.METHOD: 70 | case INTERCEPTOR.PROTOTYPE: 71 | this.addMethodInterceptor(method, advice, interceptor, callback); 72 | break; 73 | default: 74 | break; 75 | } 76 | }, 77 | /** 78 | * add method interceptor 79 | * @param {[String]} method [method name of module] 80 | * @param {[String]} advice [intercep occasion] 81 | * @param {Function} callback [intercep action] 82 | * @return {[void]} 83 | */ 84 | addMethodInterceptor: function(method, advice, interceptor, callback) { 85 | if (typeof interceptor == "function") { 86 | callback = interceptor; 87 | interceptor = INTERCEPTOR.METHOD; 88 | } 89 | interceptor = interceptor || INTERCEPTOR.METHOD; 90 | 91 | var instance = resolveInstance(interceptor, _instance); 92 | if (typeof instance[method] == "function" && !_methods[method]) { 93 | delegate(instance, _methods, method); 94 | } 95 | _methods[method] = _methods[method] || {}; 96 | _methods[method][advice] = _methods[method][advice] || []; 97 | _methods[method][advice].push(callback); 98 | }, 99 | /** 100 | * return original instance 101 | * @return {[Object]} [module instance] 102 | */ 103 | instance: function() { 104 | return _instance; 105 | } 106 | }; 107 | 108 | return new Proxy; 109 | }; 110 | 111 | function resolveInstance(interceptor, instance) { 112 | switch(interceptor){ 113 | case INTERCEPTOR.METHOD: 114 | return instance; 115 | case INTERCEPTOR.PROTOTYPE: 116 | return instance.prototype; 117 | default: 118 | return instance; 119 | } 120 | } 121 | 122 | function delegate(instance, _methods, method) { 123 | // wrap the function 124 | var oriFunct = instance[method], wrapFunct; 125 | instance[method] = function() { 126 | var callback, result, args = arguments, 127 | self = this; 128 | 129 | //before instance run 130 | if (_methods[method] && (callback = _methods[method][PROXYCYCLE.BEFORE])) { 131 | _.forEach(callback, function(cb) { 132 | cb.apply(cb, args); 133 | }); 134 | } 135 | //afterreturning the instance run 136 | if (_methods[method] && _methods[method][PROXYCYCLE.AFTERRETURNING]) { 137 | var len = 0, 138 | fnCache = {}, 139 | count = 0; 140 | _.forEach(args, function(param, i) { 141 | if (typeof param == 'function') { 142 | len++; 143 | args[i] = function() { 144 | if (!fnCache[i]) { 145 | fnCache[i] = 1; 146 | count++; 147 | } 148 | //before the callback call 149 | if (count == len) { 150 | callback = _methods[method][PROXYCYCLE.AFTERRETURNING]; 151 | _.forEach(callback, function(cb) { 152 | cb.apply(cb, args); 153 | }); 154 | } 155 | param.apply(param, arguments); 156 | }; 157 | } 158 | }); 159 | } 160 | // instance throws 161 | if (_methods[method] && (callback = _methods[method][PROXYCYCLE.THROWS])) { 162 | wrapFunct = (function(cbk){ 163 | return function(){ 164 | var res; 165 | trycatch(function(){ 166 | res = oriFunct.apply(self, args); 167 | }, function(err){ 168 | _.forEach(cbk, function(cb){ 169 | var param = slice.call(args); 170 | param.unshift(err); 171 | cb.apply(cb, param); 172 | }); 173 | }); 174 | return res; 175 | } 176 | })(callback); 177 | } else { 178 | wrapFunct = function(){ 179 | return oriFunct.apply(self, args); 180 | } 181 | } 182 | //around instance run 183 | if (_methods[method] && (callback = _methods[method][PROXYCYCLE.AROUND])) { 184 | result = decorateCallbacks(callback, function() { 185 | return wrapFunct(); 186 | }, args)(); 187 | } else { 188 | //instance run 189 | result = wrapFunct(); 190 | } 191 | //after instance run 192 | if (_methods[method] && (callback = _methods[method][PROXYCYCLE.AFTER])) { 193 | _.forEach(callback, function(cb) { 194 | var param = slice.call(args); 195 | param.unshift(result); 196 | result = cb.apply(cb, param) || result; 197 | }); 198 | } 199 | 200 | return result; 201 | }; 202 | } 203 | 204 | function decorateCallbacks(cbs, callback, args) { 205 | var next = callback; 206 | _.forEach(cbs, function(cb, i) { 207 | next = decorateCallback(cb, next, args); 208 | }); 209 | return next; 210 | } 211 | 212 | function decorateCallback(cb, next, args) { 213 | var newArgs = slice.call(args, 0), 214 | outside; 215 | newArgs.push(function() { 216 | return next(); 217 | }); 218 | return function() { 219 | return cb.apply(cb, newArgs); 220 | }; 221 | } 222 | --------------------------------------------------------------------------------