1 Introduction & Conceptual Overview

HTML本身是静态的,对于现在的动态网页无能为力.为了弥合静态和动态之间 的鸿沟,有两种静态方式:库(jQuery),框架(durandal, ember). AngularJS 则提供了第三种方式,通过数据绑定等方式让浏览 器学会新的语法. AngularJS 是完全基于前端的,适用于CRUD应用.

2 DI

AngularJS中的模组类似于名字空间,在几乎所有东西都在名字空间下注册.这 些注册的东西是通过名字推断来进行绑定(引用)的.AngularJS自带的那功能,都 是可以直接引用的.自己定义的东西,也可以直接靠名字推定,但是要给模组 加上依赖关系.

service, directive, filter 都是通过 factory/directive/filter 方 法注册在模组中的,这些方法统称工厂(factory)方法.

我们可以通过 config/run 方法注册在模组配置和运行的时候需要运行的 函数,这些函数也同样的可以被以类似上面的方式来调用.这些方法被归类为 模组(module)方法.

控制器(controller),被解释为classes或者constructor function,同样需 要注册到模组中,通过名字来引用.

这些东西在引用的时候,可以直接当函数参数名,可以用 $inject 来显示 说明,也可以用数组.后两种基本上是在第一种的基础上多写些代码来说明.直 接用函数参数名是最简单的,但是不被官方所推荐.

3 Data Binding & Scopes & Template

传统模板是通过模板Tempalte和模型Model来建立视图View. AngularJS是通过模 板建立起View和Model的对应,让View跟Model直接互动. AngularJS中,template是包含AngularJS元素和属性的HTML.template中可用 的AngularJS元素和属性为 Directive, Markup (), Filter, Form controls.通过这些template,JS(model)和(view)被连接了起来(数 据绑定).

上下文(Scope)提供表达式的运行环境,它被组织成一个类似DOM的层级结构.上 下文监视表达式并传递事件.API($watch)监视模型的变更,($apply)将任意 由Angular之外的事件对模型的改变传递到View.这里所谓的Angular之外的 事件,指的是controllers,services,event handlers.

上下文之间可以嵌套,嵌套的上下文有两种 child scopeisolate scope, child scope 继承上层上下文的内容, isolate scope 不继承. 如果上下文中没有对应的属性,表达式是没有任何作用的.

上下文可以看做将应用控制器controller和视图view联系起来的胶水. 在template连接的时候,directive会设置上下文中的$watch,$watch会通知 directive任何属性的变更,从而使directive能根据更新的数据来渲染DOM 控制器controller和directive都能直接访问scope,但是不能直接访问对方.这 种设计孤立controller和directive. $rootScope 提供对root上下文的访 问,一般指向ng-app所在的范围.

如果用chrome的inspect element功能查看页面元素,会发现 class="ng-scope ng-binding"之类的属 性.Anuglar会自动将 ng-scope 加入制定了上下文的元素, ng-binding 则说明这里面的内容是用绑定实现的.

在不同层级使用同一个controller,

<div ng-controller="EventController">
  Root scope <tt>MyEvent</tt> count: 
  <ul>
    <li ng-repeat="i in [1]" ng-controller="EventController">
      <button ng-click="$emit('MyEvent')">$emit('MyEvent')</button>
      <button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button>
      <br>
      Middle scope <tt>MyEvent</tt> count: 
      <ul>
        <li ng-repeat="item in [1, 2]" ng-controller="EventController">
          Leaf scope <tt>MyEvent</tt> count: 
        </li>
      </ul>
    </li>
  </ul>
</div>
angular.module('eventExample', [])
.controller('EventController', ['$scope', function($scope) {
  $scope.count = 0;
  $scope.$on('MyEvent', function() {
    $scope.count++;
  });
}]);

这个例子中,首先我们看JS的部分.这部分注册了一个EventController,这个 controller对于给定的上下文 $scope 设定属性 count,同时注册一个 名为 MyEvent 的事件,调用一个闭包函数对于当前上下文进行操作.

接下来看HTML,里面把同一个controller用了三次,也就意味着对于 eventExample,给予三个不同的 $scope.而这三个 $scope 各自拥有自 己的 MyEvent 函数副本.最后, $emit('MyEvent') 将调用这个函数的 动作传递给上层 $scope, $broadcast('MyEvent') 则传递给下层 $scope.

$scope 的生命周期中,模型的改变不会立即触发 $watch, 而是通过在 $apply 结束时的 $digest 来触发 $watch,这么做的好处在于将多个 改变合为一个 $watch 动作. 当子上下文不需要继续存在时,子上下文的 创建者负责调用 scope.$destroy() 负责停止事件传递,允许垃圾回收来 释放内存.

4 Module & Provider

angular.module会覆盖已存在的名字.模组提供 provider, factory, service, value, constant, animation, filter, controller, directive, config, run 这些方法.除了最后两个以 外,都是注册某一种功能. provider,factory,service,value,constant 都是JS内部用的, directive,filter,animation,controller 都是在html里面用 的(JS里面定义的).

  • JS
    • provider 提供一个在config阶段配置内容的机会,通过 $get 函数返 回绑定的对象.
    • factory 注册一个名字空间,跟value类似,但是可以引用其它的服务
    • service 注册一个类,用 new 来获得对象
    • value 注册一个变量,也可以在变量下加入函数,但是不能引用其它的服务.
  • html
    • directive, 定义一个html元素,属性,类名,对相应的元素加入定义好的模块
    • controller, 注册一个可在html里面引用的属性值,对相应元素中的变 量进行绑定和操作.
    • animation, 动作效果
  • html and JS
    • filter, 过滤器. 返回一个过滤函数并绑定在这个名字上. 过滤函数接 受整个数组为第一个参数,还可以自定附加参数.

Provider是AngularJS的基础,Value,Factory,Service,Constant都建立在Provider之上.下 面的 myApp.XXX 方法实际上都是调用 $provider.XXX.

  • Value recipe
    var myApp = angular.module('myApp', []);
    myApp.value('clientId', 'a12345654321x');
    myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
      this.clientId = clientId;
    }]);
    
    <html ng-app="myApp">
      <body ng-controller="DemoController as demo">
        Client ID: 
      </body>
    </html>
    

    其实就是定义一个模组下的变量/对象,可以是简单值,也可以在这个变量 下加入函数.

  • Factory recipe 相对Value,可使用其他服务,初始化服务,惰性初始化. 使用 myApp.factory 方法.返回的是一系列变量,函数.
  • Service recipe 使用 myApp.service() 方法,注册一个构造函数,得到一个类(class), 用 new 来得到新实例.返回的是构造函数.
  • Provider Recipe 比Factory更高一层,能够让用户在使用服务之前进行定制化,用config函 数来实现.Provider里面有个 $get 方法,DI会把这个方法返回的东西绑 定到注册的Provider名字上.
    myApp.provider('AAA', function AAAProvider(){
        this.configFunc = function(bla){blabla};
        this.$get = function(lab){ return BBB};
    });
    myApp.config(function(AAAProvider){
        AAAProvider.configFunc(...);
    });
    myApp.controller('CCC', function(AAA){ /*AAA is BBB*/});
    

    provider 只能在config里面,而在run里面,只能用非 provider.

  • Constant Recipe Constant在config和run的过程中都存在(在他们之前).用 myApp.constant 来注册.
  • Special Purpose Objects 包括controller, directive, filter, animation.

4.1 Service

服务都是惰性加载和单例,通过 factory 注册.服务本身对于html是不可 见的.在JS里面引用其功能. 除了用工厂方法意外,还可以在模组的config方法中通过 $provie.factory 来 注册服务.这里的Service和前面Provider段落中的不是一个概念.

4.2 Controller

控制器是用于连接上下文和服务的,通过 controller 注册,对于html可见,通 过 ng-controller 来进行绑定.本身也可以定义一些行为.不需要返回什 么东西.

5 Directive

Directive提供自己定义的HTML标记,基于AngularJS HTML compiler ($compile). ngApp, ngRepeat 等等就是AngularJS自带的 directive,这些directive通过 ng-app, ng-repeat 标记在HTML文件中 改变元素的行为模式.当然,这里的compile并不是真正意义上的编 译,AngularJS只是将事件监听器绑定到HTML元素上,使得这些元素可以互动. 在 $compile 的API中可以找到directive的各种option的详细说明.

命名方面, -,:,_ 都可以作为分隔符, ng-modelng:model 是等 效的.同时还可以在前面加上 x- 或者 data- 前缀, ng-modeldata-ng:model 是等效的.

使用directive可以将它作为tag name, attribute name, comment 或者 class name.

<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive:my-dir exp-->
<span class="my-dir: exp"></span>

以上三种标记方式是等效的.当然,推荐使用tag name和attribute name.

ngAttr 能够绕过浏览器限制使用AngularJS的表达式设定attribute.

<circle cx=""></circle>
<circle ng-attr-cx=""></circle>
<svg viewBox=""></svg>
<svg ng-attr-view-box=""></svg>

Directive返回固定的option.如果说Service(Factory)是注册名字和对应的属性,函数;Directive则是将值和函数绑定到预先定义好的选项

5.1 注册Directive

module.directive 函数注册directive,它接受一个名字和一个工厂方法.方 法返回template,用其填充directive元素的内容.

5.2 restrict option

值为 ‘A’ (match attribute), ‘E’ (match element), ‘C’ (match class).指定这个directive被用作元素的属性,元素名或类

5.3 link option

操作DOM.这个option需要指定一个函数,函数的参数为 scope (上下文),element (当前元素),attrs (当前元素的属性), ctrl (array of controllers required by the directive).

angular.module('docsTimeDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.format = 'M/d/yy h:mm:ss a';
}])
.directive('myCurrentTime', ['$interval', 'dateFilter', function($interval, dateFilter) {

  function link(scope, element, attrs, ctrl) {
    var format,
        timeoutId;

    function updateTime() {
      element.text(dateFilter(new Date(), format));
    }

    scope.$watch(attrs.myCurrentTime, function(value) {
      format = value;
      updateTime();
    });

    element.on('$destroy', function() {
      $interval.cancel(timeoutId);
    });

    // start the UI update process; save the timeoutId for canceling
    timeoutId = $interval(function() {
      updateTime(); // update DOM
    }, 1000);
  }

  return {
    link: link
  };
}]);

设定函数 updateTime 直接操纵元素内容,通过 $watch 将元素中的属 性变动跟操作函数绑定在一起. 通过 $interval 来实现自动读秒更新时 间. 绑定 $destroy 来终止 $interval 读秒.

5.4 isolate scope

angular.module('docsIsolateScopeDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' };
  $scope.igor = { name: 'Igor', address: '123 Somewhere' };
}])
.directive('myCustomer', function() {
  return {
    restrict: 'E',
    scope: {
      customerInfo: '=info'
    },
    templateUrl: 'my-customer-iso.html'
  };
});
<div ng-controller="Controller">
  <my-customer info="naomi"></my-customer>
  <hr>
  <my-customer info="igor"></my-customer>
</div>

controller里面绑定了 naomiingor 的值,html里面将directive 元素中的 info 绑定为 naomiingor. directive里面定义独立 上下文,将 customerInfo 绑定到元素里的 info. my-customer-iso.html 里面引用 customerInfo 的内容. 如果要 引用 <div bind-to-this="thing">, 在directive里面应该用 =bindToThis.如果isolate scope里的 属性名跟directive标签中的一样,则可以缩写为 XXX: '='. 注意:如同之前提到过的,isolate scope没有继承高级上下文的内容,在这 里面调用高级上下文中的变量,只能是空 template里面或者templateUrl引用的html里面的内容继承的是directive 的上下文

5.5 transclude

给予外部上下文访问能力.同时也意味着之前isolate scope 被遮盖了,无法访问.

angular.module('docsIsoFnBindExample', [])
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
  $scope.name = 'Tobias';
  $scope.hideDialog = function () {
    $scope.dialogIsHidden = true;
    $timeout(function () {
      $scope.dialogIsHidden = false;
    }, 2000);
  };
}])
.directive('myDialog', function() {
  return {
    restrict: 'E',
    transclude: true,
    scope: {
      'close': '&onClose'
    },
    templateUrl: '<div class="alert"><a href class="close" ng-click="close()">&times;</a><div ng-transclude></div></div>'
  };
});
<div ng-controller="Controller">
  <my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
    Check out the contents, !
  </my-dialog>
</div>

点击动作为ng-click="close()",而 close 动作被绑定到 &onClose 这个directive. on-close 又在 html里面绑定到 hideDialog 函数上. 这个示例中显示了如何将函数表 达式传递给directive,让directive在一定时候选择执行这个函数.scope中 读取值用’@XX’,读取表达式结果用’=XX’,读取表达式但不执行用’&XX’.

5.6 scope

不管是template还是templateUrl都是在directive的上下文下.当没有 isolate scope的时候,直接访问外部上下文.有isolate scope的时候,只能 访问isolate scope.这个时候html里面元素的内容,比如 <AA>blabla</AA> 里面的 blabla 是被忽略/覆盖的. 当在template中加入一个含有transclude的元素的时候,html里面的 blabla 会被显示在这个元素之中,而这个含有transclude的元素的内容 却不会被显示,同时html里面的表达式无法访问isolate scope.

...
return {
    template: <div>CONTENT IN JAVASCRIPT (DIRECTIVE SCOPE (OUTER/ISOLATE SCOPE))</div><div ng-transclude>CONTENT FROM HTML (OUTER SCOPE)</div>
}

5.7 controller in directive.

6 Filters

在html里面用的时候 在JS里面使用一个叫做 filter 的过滤器

angular.module('FilterInControllerModule', []).
controller('FilterController', ['filterFilter', function(filterFilter) {
  this.array = [
    {name: 'Tobias'},
    {name: 'Jeff'},
    {name: 'Brian'},
    {name: 'Igor'},
    {name: 'James'},
    {name: 'Brad'}
  ];
  this.filteredArray = filterFilter(this.array, 'a');
}]);

创建过滤器

angular.module('myReverseFilterApp', [])
.filter('reverse', function() {
  return function(input, uppercase) {
    input = input || '';
    var out = "";
    for (var i = 0; i < input.length; i++) {
      out = input.charAt(i) + out;
    }
    // conditional based on optional argument
    if (uppercase) {
      out = out.toUpperCase();
    }
    return out;
  };
})

$filter 服务通过名字调用相应的过滤器.

7 Forms

8 Expressions

9 HTML Compiler

10 Security

11 i18n and l10n

12 Accessibility

13 Bootstrap

14 Running in Production

15 Animations

学习AngularJS入门教程, 英文版

1 01和02,静态模板和AngularJS模板

在HTML里面用np-app来定义Angular的上下文,可以用 ng-app="AAA" 来制定一个名字空间.JS中 通过这个名字来获取HTML里的上下文 var AAA = angular.module('AAA', []);.

在HTML中Angular的范围内,用 ng-controller="BBB"标记一个div,在里 面用一些Angular命令(ng-repeat)和表达式设定显示方式.在JS里面用 AAA.controller('BBB',function ...) 给Angular表达式中的域赋值.

AAA 中的 $scope$ 是AAA的根作用域,控制器的作用域则在 BBB 之中.也 就是说,如果在AAA下面有多个 controller, controller X 的数据绑定 在 controller Y 下是无效的. AngularJS作用域文档.

略:AngularJS的开发者倾向于使用 Jasmine 行为驱动开发(BBD)框架进行 测试.

写在后面,对angular.module是所有JS中创建,注册和获取模组的地方.HTML里 面通过ng-app=”AA”来绑定模组AA的上下文

2 03 迭代器

建立输入表单 <input ng-model="query"> ,修改 ng-repeat<li ng-repeat="x in list|filter:query">.只要在输入框里输入文本,就会自动 地修改列表,只显示包含这个文本的项目.这代表两点:1. 数据绑定同名变量 query,值的同步是实时的; 2.filter过滤器,生成新数组,自动更新视图.

略:Angular端到端测试.

3 04双向绑定

在HTML里面添加选择器

<select ng-model="orderProp">
  <option value="name">Alphabetical</option>
  <option value="age">Newest</option>
</select>

同时在迭代器中加入条件 <li ng-repeat="phone in phones | filter:query | orderBy:orderProp">

JS中给每个数据加上 age 这个属性,同时设定 $scope.orderProp = 'age'设定选择器 的默认值.

select 的元素和 orderProp 之间建立了双向绑定.

4 05XHR和依赖注入

通过 $http.get(AA/BB.json) 来读取json文件. $http 是AngularJS提 供的内建服务,通过依赖注入DI子系统.

phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {
    $http.get('phones/phones.json').success(function(data) {
        $scope.phones = data;
    });
}]);

5 06链接与图片模板

<ul class="phones">
  <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
    <a href="#/phones/" class="thumb"><img ng-src=""></a>
    <a href="#/phones/"></a>
    <p></p>
  </li>
</ul>

6 07路由与多视图 & 08更多模板

$routeProvider 重定向url,把某个url路径定向到某个HTML模板上去, 并指定相对应的controller. index.html 中定义一个域,加载所有JS文件.加 载的 app.js 设定重定向规则.

phonecatApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html',
        controller: 'PhoneListCtrl'
      }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html',
        controller: 'PhoneDetailCtrl'
      }).
      otherwise({
        redirectTo: '/phones'
      });
  }]);

:phoneId 是从将URL路径的对应部分作为变量.这个变量通过 $routeParams 来访问.

最后,在index.html里面加上 <div ng-view></div> 来为当前路由把对应 视图加上去.

08更多模板 之中用 $http 加载不同的json文件来根据phoneId生成每 部手机的细节说明网页.

7 09过滤器

建立自己的 filter FF,实现更复杂的规则,然后像之前使用 | filter:query 一样用类似pipe的语法把输出传给自己的filter | FF.filter并不限定在 ng-repeat 中使用.

angular.module('phonecatFilters', []).filter('checkmark', function() {return function(input) {
    return input ? '\u2713' : '\u2718';
  };
});

AngularJS内置过滤器 uppercase, json, date, date:”MM/dd/yyy@ h:mma”.

8 10事件

通过JS在上下文中定义函数,通过 ng-click 来设定点击事件,调用函数.

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
  function($scope, $routeParams, $http) {
    $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
      $scope.phone = data;
      $scope.mainImageUrl = data.images[0];
    });

    $scope.setImage = function(imageUrl) {
      $scope.mainImageUrl = imageUrl;
    }
  }]);

$scope.setImage 就是绑定的函数.

<img ng-src="" class="phone">

...

<ul class="phone-thumbs">
  <li ng-repeat="img in phone.images">
    <img ng-src="" ng-click="setImage(img)">
  </li>
</ul>

点击图片的时候调用函数,把 mainImageUrl 的值设定为当前图片.

9 11Rest和定制服务

依赖为 angular-resource/angular-resource.js.一般我们的JS相应功能 命名为 XXXService.首先注册 Service 模块.

var phonecatServices = angular.module('phonecatServices', ['ngResource']);

phonecatServices.factory('Phone', ['$resource',
  function($resource){
    return $resource('phones/:phoneId.json', {}, {
      query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
    });
  }]);

建立一个 Phone 工厂,用 $resource 来取得数据.相应的controller改 为$scope.phone = Phone.get({phoneId:$routeParams.phoneId})$scope.phones = Phone.query().

query 方法是自己声明和定义的 query: {method:'GET', params:{phoneId:'phones'}, isArray:true}.

10 12 Animations

TODO

更多的教程 开发指南Cookbook. 推荐使用AngularJS种子项目来引导开发新项目

1 JSP

JavaServer Page(JSP)是一种动态网页技术标准.在HTML中插入Java片段 (Scriptlet),以.jsp为文件后缀.在接到客户端请求的时候,服务器根据XXX.jsp文 件生成XXXServlet.java文件,然后编译运行,最后返回结果给客户端.

1.1 Servelet

Servelet是一种 独立于平台和协议服务器端 的Java应用程序,生 成动态页面.为HTTP客户端跟HTTP服务器程序的中间层.好处是快(比CGI快), 每个用户请求被激活成程序中的一个线程.Servlet是纯粹的Java,jsp介于 html和java质检,有助于美工人员来设计界面.

1.2 CGI 和 WSGI

既然上一节提到了CGI,这里稍微展开一下.Common Gateway Interface (CGI)是一种利用程序的标准输入,输出流来完成HTTP通信的方式.HTTP本身 是文本协议,请求是由服务器(Apache)处理,而应用程序可能由各种语言来实 现.CGI则将这个文本协议以标准输入流的方式传递给服务器端的CGI程序. 与之类似的还有WSGI,原理类似,但是是Python专用的协议.对于实现了 call 的对象,将 request 经过包装作为参数传入,同时也返回进过 包装的 response.

2 Tomcat + Apache

Apache和Tomcat都是提供服务器功能的软件.Apache可以使用 PHP,CGI,Perl,Tomcat在Apache后端提供对Java的支持.Apache+Tomcat处理 JSP的时候,Apache只是转发.Apache是Web服务器,Tomcat是应用服务器,是 Servlet容器,可以单独运行.

Tomcat是Servlet的容器,可以独立运行,也可以作为现有服务器的附加(支持 Apache,IIS,Netscape).配置Tomcat的时候选择运行模式.

Java后端负责业务逻辑,数据持久化(数据库),安全等等.目前接触到的内容有 账号注册登录管理,本地化设置等等.将整个后端当做一个应用,那么在主目录 下有一个 Application.java 类作为整个应用的入口.这个类最基本的应用需要 @ComponentScan@EnableAutoConfiguration 两个标签来声明自动配 置和扫描组件;同时在 main 函数里实例化一个 SpringApplication 对象 并调用对象函数 run() 就行了. 目前的程序里还用 app.setAdditionalProfiles 在没有命令行参数的时候默认加载dev配置.

1 环境与配置加载

1.1 配置文件

SpringApplication 会自动搜索以下路径来寻找 application.properties 作为配置文件.

  1. 当前路径下地 /config 子文件夹
  2. 当前路径
  3. classpath:/config
  4. classpath

同时,SpringApplication还支持 YAML 文件,即 applicaiton.yml. 函数 SpringApplication.setAdditionalProfiles("dev") 则会额外加载 application-dev.yml (或者 application-dev.properties), 而不是 dev.yml.

1.2 获取配置内容

当我们有类继承了EnvironmentAware这个接口的时候,框架会自动调用这个类 的 setEnvironment 方法,将一个 Environment 对象交给这个类的对象 从而使这个类获得配置文件中提供的参数. LocaleConfigurationMailConfiguration 两个类展示了相关的编程方法.

public class MailConfiguration implements EnvironmentAware {
    public void setEnvironment(Environment environment) {
        this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_SPRING_MAIL);
    }
}

另一种方法是直接 @Inject 一个 Environment 的对象.

public class MailService {
    @Inject
    private Environment env;
}

<–这种方法跟之前所介绍的有什么区别,暂时不知道.–>

1.3 根据配置进行渲染

LocalResolverInterceptor. 在接受请求的时候, Interceptor 拦截请求中特定的参数值,并且调用 LocalResolver 的函数来进行相关渲 染处理.在 LocaleConfiguration 中,龙伟权通过定义Bean来指定特定的 LocalResolver, 即 AngularCookieLocaleResolver.

问题:

  1. LocaleConfigurationThymeleafConfiguration 都有方法返回 MessageSource, MailService 里面貌似引用的是 LocaleConfiguration 返回的对象,为什么?

2 持久化

对象的关联问题.Mongodb本身作为NoSql的典范,内部以文件和类似json的数 据形式来储存数据.Mongodb中一个Document可以看做关系数据库中的一行数 据,一个Repository可以看做关系数据库中的一个表(table).目前我们有三个 标记为 @Document 的类 PersistentToken,Task,User 和对应的 Repository 类.

3 业务层

3.1 Controller

控制器类都在org.team.drill.web下面,统一以 XXXResource 命名.

3.1.1 AccountResource

  • registerAccount 注册账户,接受新账户信息,验证账户,创建账户,发送激活邮件.
  • activateAccount 激活账户,通过激活key来确定当前User.
  • isAuthenticate 这个什么时候会用到?
    • HttpServletRequest request.getRemoteUser()
  • getAccount 这个貌似是浏览账户信息的时候用, 返回 UserDTO
  • saveAccount 这个是修改用户信息用, email 地址必须唯一
  • changePassword (newPassword) 这个是修改用户密码用
  • getCurrentSession 貌似是获得所有当前用户的登录记录(多地自动登录产生多个记录)
  • invalideSession 取消某个登录记录(已登录)

3.1.2 UserResource

  • getUser 通过String login获取user, 直接返回 User

登录功能是在 SecurityConfig 中间接实现的.

3.2 Service

Service 里面通过 SecurityContextHolder 来获取当前用户.

3.3 DTO

位于 web.dto 包下面的 UserDTO 类型,似乎是用来作为JS交互的数据 对象?但是内容上跟 domain 里的 User 差不多,用来保证安全.在更复 杂的应用环境中有用.

4 Security

不论是Service还是Resource,都是靠 SecurityUtils.getCurrentLogin() 来获取当前用户的.那么问题来了,SecurityUtils如何知道当前用户是谁?

public final class SecurityUtils {

    private SecurityUtils() {
    }

    /**
     * Get the login of the current user.
     */
    public static String getCurrentLogin() {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        Authentication authentication = securityContext.getAuthentication();
        UserDetails springSecurityUser = null;
        String userName = null;

        if(authentication != null) {
            if (authentication.getPrincipal() instanceof UserDetails) {
                springSecurityUser = (UserDetails) authentication.getPrincipal();
                userName = springSecurityUser.getUsername();
            } else if (authentication.getPrincipal() instanceof String) {
                userName = (String) authentication.getPrincipal();
            }
        }

        return userName;
    }
}

4.1 SecurityContextHolder

包含安全上下文信息,并且默认为/ThreadLocal/,即对于一个线程采用一 个统一的安全上下文信息.同时,=SecurityContextHolder= 中还储存了当前 与App进行交互的主体(principal)的详情,用 Authentication 对象来表 示.上面的代码示例中体现的是获取当前用户信息的标准用法.

<–spring应用内部线程到底是如何管理 的?–>

4.2 UserDetails

在以上代码片段中我们可以看到一个特殊的类 UserDetails,在获取当前主 体的时候,返回的可能是一个 String 或者一个 UserDetails 的对象.那 么这个/UserDetails/到底是什么东西,为什么它的方法 getUsername() 刚 好能包含我们的 User 类所需的 login?

UserDetails 是一个接口,包含了 getPassword(), getUsername() 等 一系列方法. 在 UserDetails 中加入我们定义的 login,这是靠实现 UserDetailsService 接口来做到的. 这个接口只包含一个方法,这个方法 接受一个 String 参数,并返回一个实现了 UserDetails 接口的对象.目 前在org.team.drill.security包中的 UserDetailsService 类里面,龙伟 权实现了这个方法,并且将 org.team.drill.domain.Userlogin 包 装到实现了 UserDetails 接口的 org.springframework.security.core.userdetails.User 类中.

到这里,我试着把 SecurityContextHolderUserDetails 串起来. 个 人认为,在通过 SecurityContextHolder.getContext().getAuthentication().getPrincipal() 来获取 UserDetails 的时候,spring通过调用实现了 org.springframework.security.core.userdetails.UserDetailsService 接口的对象的方法来尝试获取 UserDetails, 这个方法本身接受一个 String参数. 又绕回来了,这个String参数是 从哪来的?依靠http协议?还是session? 因为某种原因, getPrincipal 方法返回的是一个 Object,那么实际上给出了一种可能性,就 是通过某个实现了 UserDetails 的类使用额外的业务逻辑.我们可以创建 一个实现了 UserDetals 的类 A,在 UserDetailsService 中返回 A 的对象,然后将 getPrincipal() 返回的对象强制转换为 A 并调用相关 方法.当然,强制转换类型总是有风险的.

4.3 GrantedAuthority

Authentication 提供两个重要的方法,第一个是上面说过的 getPrincipal,另一个则是 getAuthorities .它返回一个 GrantedAuthoriy 对象数组.顾名思义,这是用来管理用户权限的.在 UserDetailsService 里面进行加载.目前drill中加载的是 SimpleGrantedAuthority 数组,每个 SimpleGrantedAuthority 里面包 含一个字符串,代表某种权限(自定义于 org.team.drill.security.Authority 中).

4.4 其它

random.nextBytes 来产生 PersistentToken 的序列号,不怕重复? 虽然16位Byte确实已经很大(10亿的4次方).

In this article, I will introduce the installation and basic use of MPI framework through octave. Matlab is widely used in many scientific domains as it enables the developers focus on the algorithms other than programming skills. However, the support of parallelism computing from Matlab is limited and expensive, especially for computing on clusters. Currently, two parallelism frameworks are widely used: MPI (Message Passage Interface) and MapReduce. Hadoop, which is an Java implementation of MapReduce, does not currently support Matlab or Octave. Therefore, I choose the MPI + Octave as a parallelism solution for Matlab users.

1 Installation

1.1 Local Machine

  • Install open-mpi.
    • On Mac OS X with homebrew, brew install open-mpi
    • On Ubuntu, the direct way is install the packages via package manager sudo apt-get install openmpi-bin openmpi-common openmpi-checkpoint libopenmpi-dev libopenmpi-X.X. However, the OpenMPI installed through this method does not work with the octave-mpi package due to a library linkage problem. A fast cue is compiling and installing it manually with specific configurations. Download the openmpi package openmpi-1.8.3.tar.gz.
      tar xzvf openmpi-1.8.3.tar.gz
      cd openmpi-1.8.3
      ./configure --dsiable-dlopen
      make
      sudo make install
      
  • Install octave
    • On Mac OS X with homebrew
      1. install XQuartz from image file downloaded from http://xquartz.macosforge.org/landing/.
      2. brew tap homebrew/science
      3. install gfortran brew install gcc
      4. brew install octave
    • On Ubuntu sudo apt-get install octave
  • Install octave mpi package
    • Inside octave
      putenv ("PATH", ["path/to/mpic++/:" getenv("PATH")]) % optional
      pkg install -forge mpi      % install mpi from internet or
      pkg install PATH/TO/PACKAGE % install mpi from downloaded package
      
    • Through package manager in Ubuntu (deprecated), run apt-get install octave-openmpi-ext. As the pakcage manager could automatically install the OpenMPI package, which does not work with the octave-mpi actually, we suggest do NOT use this method.

1.2 Virtual Machine

2 Ocatve

2.1 Run octave code with mpi.

  • First way is run code from shell/terminal/ssh.
    mpirun [OPTIONS] octave -q --eval '[octave code, function or script name]'
    
  • Second way is call the system command inside octave.
    system ("mpirun octave -q --eval '[octave code, function or script name]");
    
  • Octave mpi package contains some simple examples to show you how to write the octave code that enables the parallelism via MPI. These codes are in two possible locations if you install the octave mpi package globally:
    • /usr/share/doc/octave-mpi/examples/
    • /usr/share/octave/packages/mpi-1.2.0/

    If you install the package under user’s home folder, you can find exmaple codes under $HOME/octave/packages/mpi-1.2.0/. To test the example inside octave, run command [info res] = system ("mpirun octave -q --eval 'pkg load mpi; helloworld ()'");. The displayed string of the funciton, e.g. helloworld(), will return by this invokation. Therefore your can embed it in another serial computing octave script context (use str2num()).

  • Frequently used mpirun options: --hostfile indicate the file that contains the hostnames of cluster; -np number of processes to run.

2.2 Programming

The interface provided by octave mpi package is a subset of all MPI functions.

Example 1, basic example about initializing the environment, send and receive messages/variables. Basically, these functions are enough to create the parallelism program.

MPI_Init();
communicator = MPI_Comm_Load("a label string") % one can use multiple communicators indicated by different label
my_rank = MPI_Comm_rank(communicator) % rank is the indicator(id) of current process
p = MPI_Comm_size(CW) % the size of a communicator is the number of processes handled by the communicator
[info] = MPI_Send(value, ranks, TAG, communicator);
% value:  a octave variable
% ranks:  a vector that contains the id of destination processes, can be an integer
% TAG:    an integer to identify the message (when there are many messages sent in the same context)
% info:   an integer that indicates the success of failure state of process
[value, info] = MPI_Recv(source, TAG, communicator)
% source: the id the process that send this message
%         receive from any process if source is -1
%         process starts from 0 and process 0 is usually used as master process
% value:  received variable
MPI_Finalize()

Process sequence control functions:

  • Function MPI_Barrier(communicator) will block the process until all the processes reached this location.
  • Function MPI_Probe(RANK, TAG, COMM) test if process [RANK] send a message with TAG from communicator COMM. Block the process if no message arrive.
  • Function MPI_Iprobe(RANK, TAG, COMM) test if process [RANK] send a message with TAG from communicator COMM. NOT block the process if no message arrive.

2.3 Summary

If you run the octave with MPI, you will find multiple octave processes in the process monitor. Once you run your MPI program, multiple clones of your program are craeted. Each of them is a full clone, which means the content before MPI_Init or MPI_Finalize will be run in each process. You can test it by putting some random function before MPI_Init and see the result. So, if you have a complex application and only want to use MPI to accelerate a specific function, you should call the MPI program as a independent application and get the result in indirect way. For example, you can write the result into a file and read it in your main application. But that would require a distributed file system, which is out of the range of this article.

3 C

The MPI interface for C language is much more powerful than that for octave. But basically, it follows the same procedure: initializing MPI environment (world), communication with process id and message tag. I recommend the tutorial from [https://computing.llnl.gov/tutorials/mpi/] as a learning material. It provides many exercises for basic features of MPI in C.

Compile C source file using the command below, for more options, please check the document of the version you installed.

mpicc [YOUR_C_SOURCE_FILE]
  • Point-to-point communication
    • blocked MPI_Send(buffer,count,type,dest,tag,comm) MPI_Recv(buffer,count,type,source,tag,comm, status)
      • buffer Buffer is the variable you want to send or you want to receive the data. In C, you have to pass the buffer by reference &var.
      • count Count is the number of data element to be sent since with the address/reference you send, the reciever can read an array.
      • type MPI defiend series of data types for each C type. These types are used for transfering between processes (on network for clusters).
      • dest/source The id (rank) of process you want to send or you want to receive from.
      • tag Tag is used to identify the data you received. Since MPI is an interface that can be used to transfer data through network, it is possible that the data you send later arrives earlier. If the processes of your MPI application have to communicate multiple times, tag is necessary to receive the data and execute on them in correct order.
      • comm The communicator or called MPI_COMM_WORLD, which is the MPI context created at the beginning of application.
      • status Em, it is status, but I have no idea what it exactly is…
    • non-blocked MPI_ISend(buffer,count,type,dest,tag,comm,request) MPI_IRecv(buffer,count,type,source,tag,comm,request) First, I have to clarify the blocking/non-blocking concept in this context. It is different to the block/non-block in multiple thread programming. Blocking means the application will not continue until the data in the application buffer is completely sent out (through internet protocol). Non-blocking means the application continue without waiting. Pay attention, it wait until the data is sent out, not received by other processes. And I think that is why tag is necessary in both cases, because if the application block until data received, the tag is not necessary and the application becomes sequential. Besides, you cannot put receive before send because it is blocked.

      What is the benefit of non-blocking? You don’t have to wait for the receiving of data. In the example code below, we can see that processes receive data before sending them.

      #include "mpi.h"
      #include <stdio.h>
      
      main(int argc, char *argv[])  {
      int numtasks, rank, next, prev, buf[2], tag1=1, tag2=2;
      MPI_Request reqs[4];
      MPI_Status stats[4];
      
      MPI_Init(&argc,&argv);
      MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
      MPI_Comm_rank(MPI_COMM_WORLD, &rank);
      
      prev = rank-1;
      next = rank+1;
      if (rank == 0)  prev = numtasks - 1;
      if (rank == (numtasks - 1))  next = 0;
      
      MPI_Irecv(&buf[0], 1, MPI_INT, prev, tag1, MPI_COMM_WORLD, &reqs[0]);
      MPI_Irecv(&buf[1], 1, MPI_INT, next, tag2, MPI_COMM_WORLD, &reqs[1]);
      
      MPI_Isend(&rank, 1, MPI_INT, prev, tag2, MPI_COMM_WORLD, &reqs[2]);
      MPI_Isend(&rank, 1, MPI_INT, next, tag1, MPI_COMM_WORLD, &reqs[3]);
      
            {  do some work  }
      
      MPI_Waitall(4, reqs, stats);
      
      MPI_Finalize();
      }
      
  • Collective communication Basically, all the collective communications can be implemented using the point-to-point communication methods.
    • MPI_Reduce Split data to processes, gather the result as reducing operation such as the sum of array.
    • MPI_Scatter Split data from one processes to all the processes.
    • MPI_Gather Gather data from all the processes to one process
    • MPI_Barrier For synchronizing.
  • Derived data type Though the MPI provides series of primitive types, it is also useful to let useser to define their own data type. You can define a variable of type and use it as the MPI type.
    1. Contiguous TODO
    2. Vector TODO
    3. Indexed TODO
    4. Struct TODO
  • Group and communicator management Group is an ordered set of processes, where the id(rank) of process go from zero to N-1. Communicator encompoasses a group of processes. Communicator that comprises all tasks is represented by MPI_COMM_WORLD. They are the same thing for programmer.

    Groups can be destroyed and created during execution. One process can be in multiple groups.

  • Virtual topology routine Organize the processors in geometric way, like orginizing the processors in a two-dimensional array.

4 Performance Comparison

After the installation and the configuration of jekyll along with the org-mode. I tried to find some configurations to facilitate the creation of org files (create file with date in front of file name, insert heads, etc.) I did not realize the existence of relative emacs packages in elpa and asked google for solutions. My inefficient search key-words let the google returns undesired answer: the octopress. Octopress is based on the jekyll, but provide richer blog settings. Finally, I migrated to octopress and use it to manager my github pages.

1 Octopress Installation

Installation of octopress is little complex since it requires low version ruby.

1.1 Installation of old version ruby with rbenv

You can following the instructions on the official manual.

cd # go to home
git clone git://github.com/sstephenson/rbenv.git .rbenv
git clone git://github.com/sstephenson/ruby-build.git .rbenv/plugins/ruby-build

Add two lines into the end of shell profile (.zshrc in my case).

export PATH=$HOME/.rbenv/bin:$PATH
eval "$(rbenv init -)"

Then reload the profile by source ~/.zshrc or resume the terminal. The installation of rbenv and ruby-build can be replaced by

brew update
brew install rbenv
brew install ruby-build

Attention: the shell profile must be modified with both installation methods.

Then install ruby 1.9.3, which is required by octopress.

rbenv install 1.9.3-p0
rbenv local 1.9.3-p0
rbenv rehash

1.2 Setup the octopress

My setup experience is basically the same with the official manual. However, after the installation, I replaced the octopress directory to ~/WorkSpace/xiaoliuai.github.io because it actually becomes the repository of my github pages (magnificent!). So I suggest to clone the octopress git repository into the directory with the same name of the repository of your github pages at the beginning.

1.2.1 Copy octopress repository

(_I suggest to clone shallow copy with option_ depth)

git clone git://github.com/imathis/octopress.git octopress
cd octopress

1.2.2 Install dependencies

gem install bundler
rbenv rehash    # If you use rbenv, rehash to be able to run the bundle command
bundle install

1.2.3 Install the default theme

(_I suggest to ignore this step and install the 3rd-party theme directly_)

rake install

2 Common usage

  • Local site

    The octopress puts the generated web site, including the pages, layouts, images, etc. into the public folder for local preview. The source folder contains all the source files used to generate the site, including the layout HTML files, Java-scripts, style-sheets, etc. It can be seemed as a hyper _posts folder that contains source files more than blog HTML files. The familiar _posts folder used in jekyll is under this folder. Let’s start blogging.

    First, go into the octopress folder, type rake new_post["title"], octopress will ask you to give a blog tile, then creates a markdown file into the _posts folder with jekyll format (concatenate date and the title as the file name).

    rake new_post["My first blog"]
    # create source/_posts/2014-10-28-my-first-blog.markdown
    

    This file should be edited respect to the jekyll protocol.

    Second, ran rake generate to generate the site in public and then ran rake preview to mount a web-server at http://localhost:4000.

  • Deploying to Github Pages

    Octopress integrated commands to let you deploy the generated site to the github.

    rake setup_github_pages
    

    This command will ask you for a URL of the Github repo, then it will store your Github pages repository URL as the origin remote and change the octopress remote to octopress. The directory of octopress becomes the repository of the source branch of the origin remote (your Github Pages repo). The _deploy directory, becomes the master branch of the origin remote. Run command:

    rake deploy
    

    will update the files in public to _deploy, then push the changes to the master branch of the origin remote. To keep your settings and sources tracked, run

    git add .
    git commit -m'Your message'
    git push origin source
    

    to push the contents in octopress directory to the source branch of the origin remote.

    One thing confusing is that one repository (master branch) is under another repository. But the directory _deploy is in the gitignore of source branch, hence solve the self-contain problem.

3 Emacs Configurations

3.1 Lisp package org-jekyll

There are lisp packages in melpa that provide smart org-jekyll edit functions.The great advantage of this package is that it generates blogs with respect to the entries with blog tag. In detail, it generates HTML files with proper time stamp before the file names. Therefore you don’t have to take care about the file names (you have to manage the org file name if you use other tools). Note you have to specify the time stamp in property of entry. This package does only export the section names, modify the elisp script following the issue to export the contents. Use org-jekyll-export-blog to export the blog HTML files.

;; change
(org-map-entries (lambda () (org-jekyll-export-entry project)
                               "blog|BLOG"))))
;; to
(org-map-entries (lambda () (org-show-subtree) (org-jekyll-export-entry project))
                               "blog|BLOG"))))

3.2 Org-octopress package

The package can be installed from elpa, but it does not support the tags. Hence, I modified the ox-jekyll.el file, duplicated all the variables and expressions contain categories and then replace them by tags. After reopening emacs, it successfully exports the tags into HTML files under _posts.

3.3 Conflict and Test

标准的org-mode模式在导出为jekyll的HTML片段的时候,有一些导出格式需要 相应的css或者header支持. 不兼容列表

  • 下划线
  • 表格竖边框

3.4 TODO [1/4]

  • [X] tags
  • [ ] functions of contents in setupfile.org
  • [ ] org-octopress generate publish folder.
  • [ ] In org-jekyll, YAML markup is mentioned many times. I have to study into it when I have time.

This is my first blog that is generated using org-mode, jekyll and is published on GitHub. I made this page following the instructions on org-jekyll.

1 Install necessary softwares org-mode in emacs, jekyll.

Note that the built-in org-mode in emacs might be outdated, the best solution is to remove the built-in org-mode and reinstall it from the package manager via M-x list-packages. Jekyll can be installed through RubyGem. On my OSX, the ruby and gem are preinstalled, so gem install jekyll works for me.

2 Initialize the web folder by jekyll.

As my final goal is posting the blog to GitHub, the working folder of my blog is xiaoliuai.github.io.

jekyll new xiaoliuai.github.io
cd xiaoliuai.github.io
jekyll serve

Then you can see the portal page by opening localhost:4000 in your web browser.

Now I would like to introudce the directory hierarchy of jekyll. Since I will move to octopress, which is a wrapper of jekyll with different directory setting, I just introduce the essential part to make the things work.

xiaoliuai.github.io/
-- _posts/
-- _sites/
...

Two main directories need special attention, that are, _posts and _sites. The first folder contains the source files, which are markdown file, html file, etc. as input for jekyll; the second folder contains the generated static server pages. All the project should be uploaded to github to be displayed where the index.html file is the portal of web site. The GitHub Pages understand this directory configuration and will automatically display the contents in _sites.

3 From org-mode to jekyll

3.1 Configuration of emacs org setting

("blog-org"
 :base-directory "~/Emacs/org/blog"
 :base-extension "org"

 :publishing-directory "~/WorkSpace/xiaoliuai.github.io/"
 :recursive t
 :publishing-function org-html-publish-to-html
 :headline-levels 4
 :html-extension "html"
 :body-only t ;; Only export section between <body> </body>
 :with-toc nil ;; don't export toc, it clobbers the YAML front matter
 )
("blog-static"
 :base-directory "~/Emacs/org/blog"
 :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|swf\\|gz\\|txt\\|el"
 :publishing-directory "~/WorkSpace/xiaoliuai.github.io/"
 :recursive t
 :publishing-function org-publish-attachment
 )
("blog":components ("blog-org" "blog-static"))

With this org-mode configuration, you have to create a folder _posts under the ~/Emacs/org/blog, then org-mode will export the folder and html files into the right place.

3.2 The head inserted into the org file for blog.

#+STARTUP: showall indent
#+STARTUP: hidestars
#+BEGIN_HTML
---
layout: default
title: Xiao Liu's First Blog
excerpt: First blog with org-mode, jekyll.
categories:
    - org
---
#+END_HTML

3.3 Comments

The two lines with comments form the main differences to ordinary html exportation. Jekyll will read the body of html files generated by org-mode and add it’s own heads to specify the styles. Org-mode will export the table of contents (TOC) at the beginning of exported html file, hence mask the YAML Front Matter. Disable the TOC can sovle this problem. However, there might be other solutions to change the position of TOC.

4 Build the server.

cd xiaoliuai.github.io
jekyll build
jekyll serve

5 Configuration

The simplest personalization of the blog site is editting _config.yml under the project directory. Following the attribute names in this file, you can easily understand how to specify the blog name, description, email address, etc.