NO END FOR LEARNING

Writing blog if you feel tired | 学海无涯 苦写博客

Gradle深入与实战(一)什么是构建工具

| Comments

什么是构建工具?

一个可编程的工具,能够以可执行和有序的任务来表达满足需要的自动化过程。

以Java为例,要得到一个简单可运行的Jar文件,需要下面几步:

1.编译源代码
2.运行测试(前提是你有测试) 3.拷贝Class文件到目标目录
4.打包Class文件为Jar文件

这是一个完整的可自动化的过程,在没有构建工具之前,是由谁来做?IDE。一个强大的IDE,以上的步骤都只需要按几个按钮,这让开发人员的生活变得很美好,完全集中在写出优秀的代码。

现在,本来整个开发过程只需要你一个人,随着任务的难度和复杂度的加剧,你的团队从一个人变成了3个人或者更多。这时,你肯定会需要代码集成,这个问题好解决,使用版本控制,无论是中心式的SVN还是分布式的Git,总之可以既可以解决版本问题,也解决代码集成的问题。

这种情况下,在没有自动化构建时,你肯定会遇到下面几个问题:
1.在我的机器上可以跑
2.从版本控制check out代码,发现编译不过,有人少提交了代码文件
3.有个人提交代码时没跑测试,导致其他人check out代码后,测试跑不过
4.版本发布时,由一个人来check out所有代码,在他自己的机器上编译打包,结果部署到服务器上运行不了

导致上面这些问题出现的原因都有两个特点:
1.手动介入
2.重复任务

开发人员要关注的应该是编写实现功能的代码,至于编译编译代码,拷贝文件,运行测试等一切重复和可自动化的事情都应该交给机器去做,因为人是容易犯错的。

Java世界的构建工具

在Java的世界里,目前在被使用的常用构建工具有三个:Ant,Maven,Gradle。

Ant的核心是由Java编写,采用XML作为构建脚本,这样就允许你在任何环境下,运行构建。Ant基于任务链思想,任务之间定义依赖,形成先后顺序。缺点是使用XML定义构建脚本,导致脚本臃肿,Ant自身没有为项目构建提供指导,导致每个build脚本都不一样,开发人员对于每个项目都需要去熟悉脚本内容,没有提供在Ant生态环境内的依赖管理工具。

Maven团队意识到Ant的缺陷,采用标准的项目布局,和统一的生命周期,采用约定由于配置的思想,减少构建脚本需要的编写内容,活跃的社区,可以方便找到合适的插件,强大的依赖管理工具。缺点是采用默认的结构和生命周期,太过限制,编写插件扩展麻烦,XML作为构建脚本。

如果有一个构建工具可以折中,同时拥有Ant和Maven的优点,是不是很爽?告诉你有,那就是Gradle。

Gradle

基于Groovy的DSL,提供声明式的构建语言
采用标准的项目布局,但拥有完全的可配置性,就是可以改
通过插件,提供默认的构建生命周期,也可以自己定义任务,单独运行任务,定义任务间的依赖
强大的依赖管理工具,与Maven和Ivy仓库结合
与Ant天生兼容,有效的重用Ant的任务
多种实现插件的方式,强大的官方插件库
从构建级别,支持从Ant或者Maven的逐步迁移
通过包装器,无缝的在各个平台运行

看一个超级简单的例子:

如果你的项目采用标准的Maven布局(Java世界的标准布局)

1
2
3
4
5
6
7
8
9
10
src {//目录结构而已,不是代码
   main {
       java
       resources
   }
   test {
       java
       resources
   }
}

在项目根目录下,创建一个build.gradle,这个是Gradle的构建脚本文件,就和build.xml,POM.xml道理一样。

那么你要实现Java的编译,测试,拷贝class到目标目录,打包Jar文件等,只需要在构建脚本中,使用下面一句话,使用Java插件。

1
apply plugin: 'java'

然后运行gradle build。

下一篇,我们深入到实战学习Java插件的使用和依赖管理,让你快速开始Java应用开发。

参考资料:
1. 《Gradle in Action》Gradle实战

开始!AngularJS!(八)- 路由

| Comments

传统的Web应用,以Java栈为例,无论是纯Servlet还是MVC框架,比如,SpringMVC,Struts。在页面上的导航都比较简单,只需要在浏览器地址栏输入URL,一个URL用来指向服务器上一个单一的物理资源(页面文件)。当页面加载后,就可以点击链接跳转到其他资源,或者使用前进后退按钮跳转到已访问的页面。

通过修改DOM,动态生成页面的Web应用改变了这一现状,因为对页面内容的改变是直接对页面DOM元素的修改,而不是向服务器发出请求。于是,前进后退按钮在这种情况受到了影响。

URL

那么,对于单页应用,如何能够修改浏览器的URL,让浏览器可以前进后退,但是又不会向服务器发出请求?

HashBang URL

通过修改URL定制中#符号后面的部分,而不会触发当前页面重新加载。比如, http://localhost:3000/#/user/123 ,浏览器会选取URL中#号后面的差异部分来提供前进后退。

另一种方式,HTML5的historyAPI

这里首先要说明浏览器的前进和后退都依赖于浏览器的history堆栈(window.history对象),保证浏览器history堆栈的记录正确,前进后退的按钮就可以正常工作。HTML5中的history正好提供了方法来将URL推送到浏览器的history堆栈。然后只要监听window.onpopstate事件就可以修改应用的状态。

但是当直接在服务器上输入URL时,还是会向服务器发送请求,这个时候还需要在服务器端做些事情,让服务器始终返回应用首页。所以这种方式会相对麻烦一些。

当看到#号,你可能会想到另一个东西,HTML中的锚点,定位页面的位置。在HashBang方式下,需要借助AngularJS的一个服务$anchorScroll以及两个#来实现锚点。比如 http://localhost:3000/#/user/123#name

使用AngularJS的路由服务

AngularJS内置了$route服务来处理Web应用的路由,并通过ng-view指令来显示匹配路由的内容,看下面的例子:

1
2
3
4
<body ng-app="routeApp">
    <a href="#/admin">admin</a>
    <div ng-view></div>
</body>

被插入的模板,动态渲染部分

1
2
3
<div ng-controller="adminController">
   I am admin.
</div>
1
2
3
4
5
6
7
8
9
angular.module('routeApp', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/admin', {
        templateUrl: 'pages/admin/admin.html'
    });
}])
.controller('adminController', ['$scope',
        function ($scope) {}
]);

这里引入了.config方法,需要穿插介绍一点知识:模块的生命周期。AngularJS将模块的生命周期分为两个阶段,配置阶段和运行阶段。其中模块的.config方法可以用来注册一些需要在模块加载时候执行的动作或者进行的配置,比如这里的路由配置。

AngularJS的路由服务在angular-route.js中,所以首先需要引入它,然后引入子模块ngRoute,在config方法中注入$routeProvider服务,然后剩下的就很清楚了,和Java技术栈中道理一样,URL和资源文件的Mapping。

ng-view指令可以通过当前匹配的路由找到要显示的内容。

URL查询参数

http://localhost:3000/#/user?id=123 或者 http://localhost:3000/#/user/123

几乎所有Web应用,遇到这样的URL是太平常的事情了,那么这种动态的URL应该怎么匹配呢?在AngularJS中,URL中任何以冒号(:)开头的字符串都会作为通配符。无论是这里的$routeProvider.when(/user/id=:userid)还是之后会介绍的与后端restful api通信的$resource服务在定义URL的时候。

那么如何在controller中获取该参数呢?$routeParams服务。我们可以在controller中注入该服务,看下面的例子。

1
2
3
4
5
6
7
8
9
10
11
angular.module('routeApp', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/user/:userid', {
        templateUrl: 'pages/user/user.html'
    });
}])
.controller('userController', ['$scope, '$routeParams',
       function ($scope, $routeParams) {
          $scope.userid = $routeParams.userid;
      }
]);

多个控制器重用模块

在上面的例子中,url对应的页面文件是这样的:

1
2
3
<div ng-controller="adminController">
   I am admin.
</div>

模板的作用域固定为adminController的,如果不同路由下的其他controller也想用该模块,那么就得重新建一个新的html文件。

还有一种办法,就是将controller的声明放在路由的定义中。

1
2
3
<div>
   I am admin.
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
angular.module('routeApp', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
    $routeProvider
    .when('/admin', {
        templateUrl: 'pages/admin/adminOrSuperUser.html',
        controller: 'adminController'
    })
    .when('/superUser', {
        templateUrl: 'pages/admin/adminOrSuperUser.html',
        controller: 'superUserController'
    });
}])
.controller('adminController', ['$scope',
        function ($scope) {}
])
.controller('superUserController', ['$scope',
        function ($scope) {}
]);

这样多个控制器就可以使用相同的模板。

指定默认路由

就和编程语言中的switch一样,在$routeProvider还提供了otherwise方法来设置默认路由,很明显它只能是一个,一般的做法是通过redirectTo属性,跳转到一个已有的路由上。

1
2
3
config(['$routeProvider', function($routeProvider) {
  $routeProvider.otherwise({redirectTo: '/home'});
}]);

下一节,学习通过$Resource与后端restful的API通信。

参考资料:

  1. 精通AngularJS