使用 Scala 开发不可避免地会涉及与 Java 的混合编译,这里记录下这两天的发现。 全文的基础如下:
图片来源:如何在 Java App 中導入 Scala,另外本文也大量参考了 sbt 官方文档和 Alvin Alexander 的 Scala Cookbook, 先行谢过。
在 Scala 中使用 Java - sbt
Scala 下的构建工具首推 sbt, sbt 可以简化软件构建和依赖管理过程,同时还可以使用 Scala 语言定制复杂的 task.
目录结构(Directory Structure)
受 Java 平台的 Maven 影响,sbt 使用的目录结构和 Maven 类似,一个基本的 sbt 目录结构如下所述:
sbtProject
├── build.sbt
├── lib <unmanaged dependencies jars>
├── project
│ └── Build.scala (optional)
├── src
│ ├── main
│ │ ├── java <main Java sources>
│ │ ├── resources <files to include in main jar here>
│ │ └── scala <main Scala sources>
│ └── test
│ ├── java <test Java sources>
│ ├── resources <files to include in test jar here>
│ └── scala <test Scala sources>
└── target <compiled classes, packaged jars, managed files, caches, and documentation>
目录结构的初始化可以通过 Giter8 或者自定义 shell 脚本完成,以下是一个简易的目录生成脚本。
#!/bin/sh
mkdir -p src/{main,test}/{java,resources,scala}
mkdir lib project target
touch project/Build.scala
# create an initial build.sbt file
cat <<EOF > build.sbt
name := "sbtProject"
version := "1.0"
scalaVersion := "2.10.5"
EOF
构建控制
构建过程由项目目录下的build.sbt
和子目录project
下的*.scala
共同控制。
依赖库管理
依赖的库可通过 Unmanaged 和 Managed 两种方式添加,Unmanaged dependencies 通常放置于当前项目目录下的子目录lib
, sbt 会将其作为 classpath.
另一种常见的方式为 Managed dependencies, 底层使用 Apache Ivy 作为库的依赖管理。添加依赖库在 sbt 中十分简单,最简洁的方式可以在build.sbt
中添加一行:
libraryDependencies += groupID % artifactID % revision
对于多个依赖,自然可以写多行libraryDependencies
, 也可使用Seq
合并多行。
实战1 - 使用 Java 源码进行联合编译
首先我们使用 Java 新建一 Employee 类,然后在 Scala 中调用之,目录结构如下:
sbtTest
├── build.sbt
├── lib
├── project
│ └── Build.scala
├── src
│ ├── main
│ │ ├── java
│ │ │ └── me
│ │ │ └── yuanbin
│ │ │ └── java
│ │ │ └── Employee.java
│ │ ├── resources
│ │ └── scala
│ │ └── me
│ │ └── yuanbin
│ │ └── testproject
│ │ └── Hello.scala
│ └── test
│ ├── java
│ ├── resources
│ └── scala
└── target
我们在 Java 中新建 Employee 类:
package me.yuanbin.java;
public class Employee {
private String name;
public Employee(String n) {
name = n;
}
public String getName() {
return name;
}
}
Scala 中使用 Java 中创建的类:
package me.yuanbin.testproject
import me.yuanbin.java._
object Hello {
def main(args: Array[String]) {
val p = new Employee("Bill Ryan")
println("Hello from " + p.getName())
}
}
在项目的基目录下运行sbt run
应该就能看的Hello from Bill Ryan
字样了。如果想深入了解 sbt 中编译 Java 代码的话,其官网链接值得关注 - sbt Reference Manual — Java Sources
实战2 - 使用 Java 生成的 jar 包(Unmanaged dependencies)
源码和目录结构同实战1,不过为了说明问题可在生成 jar 包后删除 Java 部分的源码和 class 文件。
Note: Java 经验不足的建议看看打包相关知识,Java Fundamentals Tutorial: Packaging 不错。
- 进入 Java 源码目录下,
cd src/main/java
(打包依赖于目录结构, 否则生成的 jar 包无法正常使用) - 编译生成
.class
类文件,javac javac me/yuanbin/java/Employee.java
- 由
.class
生成 jar 包,jar cvf Employee.jar me/yuanbin/java/Employee.class
- 将生成的 jar 包放入项目的
lib
目录下供 sbt 添加至 classpath,mv Employee.jar lib/
在项目基目录下运行sbt run
应该同样能看到Hello from Bill Ryan
字样。
实战3 - 使用Maven Repository Library
本小节参考自 Simple Scala Akka Actor examples
在 sbt 中使用 Maven 仓库非常简单,将 Maven 的groupId, artifactId, 和 version 域更改为 sbt libraryDependencies 字符串即可。以 Akka Actor 为例,相应的 Maven 依赖写法如下:
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.10</artifactId>
<version>2.3.12</version>
</dependency>
转化为 sbt 的 libraryDependencies 如下所示:
libraryDependencies += "com.typesafe.akka" % "akka-actor_2.10" % "2.3.12"
将以上一行加入至项目根目录的build.sbt
下,添加 scala 源码,vim src/main/scala/Hello.scala
import akka.actor.Actor
import akka.actor.ActorSystem
import akka.actor.Props
class HelloActor extends Actor {
def receive = {
case "hello" => println("hello back at you")
case _ => println("huh?")
}
}
object Main extends App {
val system = ActorSystem("HelloSystem")
// default Actor constructor
val helloActor = system.actorOf(Props[HelloActor], name = "helloactor")
helloActor ! "hello"
helloActor ! "buenos dias"
}
在项目基目录下运行sbt run
, 终端下应该会显示hello ...
等字样。
使用 sbt 的三种常见的 Scala/Java 混合编译方法就介绍到这了。
部署单一可执行的 jar 文件
使用sbt package
的确可以生成 jar 包,但是问题来了,怎样执行这些 jar 包呢?这些 jar 包由 sbt 创建,自然是可以在 scala 解释器中运行,但是想让 Java 也能执行这个 jar 包就得废些功夫了,原因在于 sbt 生成的 jar 包中包含一些 Scala 特有的东西,而这些东西则包含在scala-library.jar 这个 jar 包中。所以我们可以总结出如下三种方法运行 sbt 生成的 jar 包。
- 将需要的 jar 包分发至 classpath 并用 scala 执行。这种方法需要在执行系统上安装有 Scala.
- 将 jar 包及 Scala 的核心库分发至 classpath, 使用 java 执行。这种方法只需要在执行系统上安装 Java.
- 使用 sbt 插件如 sbt-assembly 构建生成单一完整的 jar 包供 Java 执行,这样一来就不需要 Scala 的核心库了。
sbt 的使用可参考官方文档,另外 CSUG/real_world_scala 也可参考看看别人的实践。
在 Java 中使用 Scala - Maven
Scala 中如何使用 Java 的部分已梳理完毕,这里就 Java 中如何编译 Scala 做些记录。
这一部分可参考如下两大链接: