Scala 和 Java 的集合类型相互转换

在 Scala 和 Java 混合编程时免不了需要进行集合类型在两种语言间相互转换,更多的是在 Scala 调用 Java 的方法时把 Scala 的集合转型为 Java 的集合。典型场景是:

public void process(java.util.List<String> orderIds) {
  ......
}

上面定义的 Java 方法,如果要在 Scala 中调用它,不考虑两种语言的集体类型转换的话,可以直接传入 Java 代码要求的类型,像这样

val orderIds = new java.util.ArrayList[String]
orderIds.add("SJ001")
process(orderIds)

这样当然可以,但不能享受到 Scala 语言中集合使用的便利性,如快捷的构造,丰富的怪异的方法(++, ::, ## 等)。所以希望此时 Scala 中调用 process()  能接近这种方法

process(List("SJ001", "SJ002"))

特别是当 Java 接受一个 java.util.Map 时,能在 Scala 里直接传入 Map("key1" -> "value1",  "key2" -> "value2") 就方便许多。

用方法来完成 Scala 和 Java 间对应集合类型的转换当然没问题,但别忘了 Scala 还支持隐式转换,那就是只要在 Scala 代码中引入 collection.JavaConversions._ 对于上面的方法在 Scala 中就可以直接传入 Scala 的 List() 了。也就是 阅读全文 >>

让 Scala 测试方法名中的空格不再显示为 $u0020

在 Scala 中借助于斜撇号我们可以使用关键字或任何字符为作为变量或方法名,例如下面的方法都是合法的:

scala> def if: Unit = {}
if: Unit

scala> def 我是谁?: Unit = {}
我是谁$qmark: Unit

scala> def just do it: Unit = {}
just$u0020do$u0020it: Unit

对了,Scala 2.11.6 在显示有些字符时会进行编码,像上面的 ? 和 空格。而用 2.11.7 的 Scala  控制台下居然原样显示,不编码(这是在进一步试难时发现的)。

我们在用 Scala 写 JUnit 风格的单元测试, 由于 JUnit 不支持像 TestNG 那样在方法名上加描述,所以方法名必须完成自我描述。依照严格的方法命名的话,势必要用驼峰方式或下划线把单词分开,如

def getOneHunderIfSixtyPlusFouty: Unit = { ... }
def get_one_hunder_if_sixty_plus_forty: Unit = { ... }

注:如果用 Scala 写 Spec 测试代码另当别论,因为有些情况下必须用 JUnit 风格(就用 JMockit 时) 阅读全文 >>

Google Guice 轻装上阵

PlayFramework 已全面向 Google Guice 演进, 由于我们的项目是从 Play 2.0 开始搭建, 现虽已升级到了 2.4.6, 但对 Guice 的还没抱得那么的紧, 有点积重而慢步前挪. Google Guice 也是一个依赖注入(DI) 容器, 据说它比 Spring 快了很多很多, 是轻量级项目或框架首选, Google 出品俱是精品.

既然它是轻量级的 DI, 那么必须呈上一个最轻量级的入门, 官方的 Get Started 文档其实啰嗦了点, 对于 BillingService 依赖的 CreditCardProcess, TransactionLog, 和 Receipt 都未有交待, 算不上一个完整项目. 所以我这里尽量简化, 又不偏离一个最简单的项目模型. 那就是下面那个例子, 它实现了在不同时间发出不同的问候.

项目需要依赖 Guice 及它的子依赖, 这得看你是用什么来管理依赖的, 依照 http://mvnrepository.com/artifact/com.google.inject/guice/4.0 根据实际情况加上依赖配置, 前面这个链接默认为 sbt 的, 我还真偏爱了 sbt, 若是 Maven 就配置 Maven 的 pom.xml 吧. Guice 的引入带上以下几个依赖包

   +-com.google.inject:guice:4.0
     +-aopalliance:aopalliance:1.0
     +-com.google.guava:guava:16.0.1
     +-javax.inject:javax.inject:1

Guice 目前是 4.0. 阅读全文 >>

sbt 任务间的依赖

项目中的 build.sbt 中发现定义任务时有 task2 <<= task1 map {...} 这样任务依赖的写法, 这个  <<= 方法有点晦涩难懂, 读过 sbt in action 之后才知道这是 sbt 0.12 或之前的做法, sbt 0.13 之后不这么用了, 直接访问下 task1.value 就行, 因此前面可改写为 task2 := {task1.value; ...}, 这也使得定义任务依赖时与普通任务一致风格了. 新的写法得益于 Scala 2.10 的宏特性, 后面还会讲到.

对于依赖于多个任务的情况, 在 sbt 0.13 前面分别是这样的, 假定有三个 Task(task1, task2, 和 task3)

val task1 = taskKey[Int]("task 1")
val task2 = taskKey[Int]("task 2")
val task3 = taskKey[Int]("task 3")

如果 task3 依赖于 task1 和 task 2

sbt 0.13 前后版本中的写法分别是

像任务的 .value 属性其实是一个宏定义, 源码 阅读全文 >>

ScalaTest + Selenium 集成测试

在 Play 1 和 Play 2 中都内置了 Selenium 集成测试工具, 这里自己尝试自己单独测试用 ScalaTest + Selenium 来做简单的集成测试. Selenium 可以支持内置的无界面 Java 实现的浏览器, 也可以用外部浏览器, 如 Safari, Firefox, Chrome, IE, Opera 或移动设备的浏览器. 使用不同外部浏览的方式或用插件(Safari/Firefox 等) 或是像借助于 Chromium 来驾驭 Chrome 浏览器.

而我们这里要用的 ScalaTest 2.2.6 在包 org.scalatest.selenium 有以下几个特质 Chrome, Driver, Firefox, HtmlUnit, InternetExplorer, Page, Safari 和  WebBrowser. 由此可以看出 ScalaTest 默认支持的浏览器. 若单论 Selenium 本身, 它可强大的暂时超乎我的想像, 可以自建服务器, 选择浏览器分发测试任务. 我们知道使用 Selenium 的好处是不光可以像通常那样断言页面静态文本, 还能执行 Javascript 脚本, 所以可断言动态内容.

本人开发环境为 Mac OS, 可以成功让 Selenium 测试跑在内置 Java 浏览器, 和外置的  Safari, Chrome 浏览器中, Firefox 的插件未安装成功.

让事实说话, 仍然让一个最简单的例子自己说话, 创建的是一个 sbt 项目, 尽量把目录最简化, 只有 build.sbt 文件和 test/IntegrationTest.scala 测试代码, 内容分别为 阅读全文 >>

建立 Play 2 框架一样的目录布局

sbt 项目继承并扩展了 Maven 的默认项目布局, 加入了 Scala 代码的支持, 所以目录如 Shell 命令 mkdir -p src/{main,test}/{java,scala,resources} 生成的目录结构, 即

.
└── src
    ├── main
    │   ├── java
    │   ├── resources
    │   └── scala
    └── test
        ├── java
        ├── resources
        └── scala

这个目录目录虽然很清晰, 但把 Java 和 Scala 代码拆在两处没多大必要, 其次是层次多了点. 因使用 Play Framework 时日有点久了, 比较习惯于 Play 2 改造后的项目布局. 我们启动到 Play 2 的 activator(其实就是加入了定制的 sbt) 控制台, 用命令看它的目录布局 阅读全文 >>

sbt 中单元测试并发执行

此次研究的目的原本是要使得 Play Framwork 2 中单元测试能够并发执行, 包括 JUnit 和 Spec 的测试用例, Play 2 的 activator 就是一个 sbt 的包装. 开发中发现我们 Play 2 中的单元测试是按序执行的, 实际上 sbt 下测试用例默认是并发执行的. 之所以 Play 2 的单元测试是按序的, 是因为 activator 设置了把 sbt 的两个属性 fork in Test := trueparallelExecution in Test := false, 见 PlaySettings.scala, 它们默认分别为 false 和 true. 这使得默认设置下 Play 2 中的所有测试无法并发执行.

sbt 默认的 fork 是 false, Play 2 改为 true 之后便可以使用 javaOptions in Test := "-Dkey1=value1" (注: 如果 fork 为 false 的话, javaOptions 将无效.) 往单元测试中参数了, 这也是为什么在 Play 2 的单元测试中无法获得启动 sbt 时(像 sbt -Dkey1=value) 的参数, 不同一个 JVM 啊.

那是不把 Play 2 的 fork in TestparallelExecution in Test 分别改回成 false 和 true 就可以让测试用例并发执行了呢? 答案是 Yes. 但我们得相信 Play 2 把它们预设为 true 和 false 是有它的用意的, 比如集成测试的每个用例都会开启本地的 3333 端口, 如果让两个集成测试同时执行将会造成端口冲突. 细致说来, Play 2 其实是懒政, 只管一刀切而让所有测试按序执行而影响了效率, 如能利用好 sbt 的测试分组机制是可以达到测试的并发执行的.

这里引出 sbt 执行测试的几个机制:

1) sbt 总是对测试进行分组, 默认时所有的测试都包含在 <default> 组中, 可用 show testGrouping 查看, 如

阅读全文 >>

查看 sbt 项目的依赖关系树

sbt 是借助于 ivy 来管理项目依赖, 像 Maven 项目中可以用 dependency:tree 来显示依赖树, 那么对于 sbt 项目该如何查看项目依赖关系呢? 本文提及了三种方式来显示项目依赖, 它们是 Shell 脚本, 自定义 sbt 任务, 和 sbt-dependency-plugin 方式. 最后一个办法使得我们也能用 dependencyTree 显示出 Maven 的  dependency:tree 效果来, 还有更酷的的.

> dependencyTree
[info] default:test_2.10:0.1-SNAPSHOT [S]
[info]   +-ch.qos.logback:logback-classic:1.0.13
[info]     +-ch.qos.logback:logback-core:1.0.13
[info]     +-org.slf4j:slf4j-api:1.7.5
[info]
[success] Total time: 0 s, completed Apr 5, 2016 12:29:53 AM

下面是探索的全部过程.

通过 sbt 控制台的 tab  自动完成或用 help .*[Dd]ependenc.* 命令再进一步过滤出与依赖比较接近 sbt 控制台任务 阅读全文 >>

Java 构建工具及 sbt 最快速体验

应对 Java 项目, 我们大概有以下几个自动化构建工具:

  1. Ant -- XML 化跨平台批处理, 配置文件 build.xml, 执行的是 target
  2. Maven -- 开始标准化目录布局, 基于项目对象模型, 配置文件 pom.xml, 执行的是 phase/goal
  3. Gradle  -- 使用 Maven 默认布局, Groove 语言铸就, 配置是 groovy 语法的 build.gradle 文件, 执行的是 task
  4. Buildr  -- 默认也是 Maven 目录布局, Java 世界被 Ruby 插手, 配置是 ruby 语法的 buildfile 文件, 也是 task
  5. Leiningen -- 也采用 Maven 目录布局, Clojure 写的, 可用于构建 Java 和 Clojure 项目, 配置文件是 Clojure 语法的 prject.clj, 基于 task
  6. sbt  -- 默认 Maven 目录布局, Scala(Simple) Build Tool, Scala 写的, 构建 Java 和 Scala 项目, 配置是 Scala 语法的 build.sbt, 基于 task. 交互式控制台.

Ant 让我们摆脱了对系统平台的依赖, 终于不同人构建的工件是一样的了, 曾经它就是昭示着敏捷. 除了 Ant 需要我们定义所有的 target 外, 其他构建都内置了基本足够用的 task, 而且也都采用了业界接受的 Maven 目录布局. 也是从 Maven 开始引入了项目依赖管理, 所以 Maven 才是里程碑式的.

在我们搜索 Java 第三方依赖时常常进到类似这样的页面 http://mvnrepository.com/artifact/com.google.guava/guava/19.0

java-dependencies 阅读全文 >>

拆分 Playframework 2 的 routes 为多个文件

我们用 Playframework 2 时,当 routes 中太多的路由配置时,我们可能会考虑把它们归类分布到多个文件中去。比如按 API 或用途分,有些是 RESTful API,有些是 Web 页面的,对于这种情景,我们可以由以下几个文件来组织:

1. routes 文件,这个仍然是充当入口

这里穿插着来解释下,-> 是固定写法,表示要去别外寻找了,紧接着的 /, /api, 和 /web 是分类路由的上下文了,例如,访问 api.routes 中定义的 /customers 的完整 API 路径就是 /test/customers。最后一部分是全类名,并非指别的路由文件的名称,像文件 general.routes 编译后会生成 general 包下生成  Routes.scala 文件,即类为 general.Routes. 阅读全文 >>