java jar 最小依赖包-java jar 执行jar包
我已经使用 Scala 很长时间了。 SBT是最方便的构建工具,但是我总是遇到一些SBT带来的奇怪问题。 今天,我将通过本文研究SBT的常用用法,并尝试克服大部分坑。
SBT常用命令
clean:清除目标文件夹下生成的所有文件。
编译:编译src/main/scala、src/main/java和项目根目录下的文件。
run:编译代码,运行项目中的main方法。 如果有多个,SBT 会要求您选择一个。
package:将src/main/scala、src/main/java、src/main/resources下的文件打包成jar包。
test:编译并运行所有测试用例。
doc:为您的 Scala 源代码生成相应的 API 文档。
reload:重新加载所有sbt构建相关的文件,比如build.sbt、project/*。
publish:将你的项目发布到远程仓库。
publishLocal:将项目发布到本地ivy仓库。
如何查看执行命令的关键输出
使用last 命令查看上次执行命令的关键输出,如warn 或error 信息。 比如你用compile编译,输出一堆log,最后报错,如果你想看看是哪部分有问题,可以用last compile命令打印出来。
使用 SBT 和 ScalaTest 进行测试
首先在build.sbt中添加scalatest包:
"org.scalatest" %% "scalatest" % "3.0.5" % Test
然后在src/main/scala下创建一个Hello.scala的源文件:
package ink.baixin.scalalearning.sbt
object Hello extends App {
val p = Person("Ink Bai")
println("Hello from " + p.name)
}
case class Person(var name: String)
然后在src/test/scala下编写相应的测试用例:
package ink.baixin.scalalearning.sbt
import org.scalatest.FunSuite
class HelloTests extends FunSuite {
test("the name is set correctly in constructor") {
val p = Person("Ink Bai")
assert(p.name == "Ink Bai")
}
test("a Person's name can be changed") {
val p = Person("Will")
p.name = "William"
assert(p.name == "William")
}
}
现在就可以使用sbt test命令来执行测试用例了,如下:
$ sbt test
...
[info] HelloTests:
[info] - the name is set correctly in constructor
[info] - a Person's name can be changed
[info] Run completed in 581 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 6 s, completed Aug 22, 2018 11:55:13 AM
如何管理依赖关系?
build.sbt 中的配置行必须用空行分隔。 这是一个简单但全面的文件,具有一个依赖项:
name := "scala-learning"
version := "0.1"
scalaVersion := "2.12.4"
libraryDependencies ++= "org.scalatest" %% "scalatest" % "3.0.5" % Test
如果要添加多个依赖包,可以定义一个Seq:
libraryDependencies ++= Seq(
"io.netty" % "netty-all" % "4.1.25.Final",
"org.apache.calcite" % "calcite-core" % "1.16.0",
"org.scalatest" %% "scalatest" % "3.0.5" % Test
)
从上面的例子可以推断,build.sbt的入口其实是一个简单的键值对,其中name、version、scalaVersion等是最常见的SBT键。 SBT 通过创建一个大 Map 来构建一个项目。
这种方式是管理依赖的方式(直接把jar包放在项目的lib目录下是一种非管理依赖方式),可以自动下载和管理某个jar包需要的其他依赖包。 SBT 底层使用 Apache Ivy 来管理依赖。 Ivy也可以用于Ant和Maven,这样你就可以通过SBT方便的在Scala项目中使用Java包。
一般添加依赖包有两种方式。 第一个需要指定 groupID、artifactID 和 revision:
libraryDependencies += groupID % artifactID % revision
例子是:
libraryDependencies += "org.scalatest" % "scalatest_2.12" % "3.0.5"
第二个有一个可选的参数配置:
libraryDependencies += groupID % artifactID % revision % configuration
或者上面的例子:
libraryDependencies += "org.scalatest" % "scalatest_2.12" % "3.0.5" % Test
可以看到多了一个Test,这个描述只是为了测试用的,这个配置可以理解为类似于Maven中的scope。 下面是对应的Maven配置:
org.scalatest
scalatest_2.12
3.0.5
test
可以看到上面的scalatest_2.12有2.12,其实就是Scala版本。 只有基于 Scala 的 Jar 包才会有这个依赖的 Scala 版本号信息。 而我们在build.sbt中通过scalaVersion设置了Scala版本,那么是不是可以省略这个2.12或者使用scalaVersion的参数设置呢? 答案是肯定的,通过以下形式:
libraryDependencies += groupID %% artifactID % revision % configuration
细心的读者应该能看出来,第一个%变成了%%。 在groupID和artifactID之间加上%%后,artifactID不需要指定Scala版本,SBT会自动将自己设置的Scala版本添加到这个值之后,如下:
scalaVersion := "2.12.4"
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % Test
相当于:
libraryDependencies += "org.scalatest" % "scalatest_2.12" % "3.0.5" % Test
如何为项目创建子项目
首先创建两个子项目,目录结构如下:
├── subproject1
│ ├── build.sbt
│ └── src
│ └── main
│ └── scala
│ └── Foo.scala
├── subproject2
│ ├── build.sbt
│ └── src
│ └── main
│ └── scala
│ └── Bar.scala
子项目的build.sbt如下:
// name := "scala-learning subproject2"
name := "scala-learning subproject1"
version := "0.1"
scalaVersion := "2.12.4"
Foo.scala 如下:
package ink.baixin.scalalearning.subproject1
case class Foo (name: String)
Bar.scala 如下:
package ink.baixin.scalalearning.subproject2
import ink.baixin.scalalearning.subproject1._
case class Bar(name: String)
object HelloFoo extends App {
println(Foo("Hello Foo"))
}
然后在根目录下的build.sbt中添加如下内容:
lazy val subproject1 = (project in file("subproject1"))
lazy val subproject2 = (project in file("subproject2")).dependsOn(subproject1)
// aggregate: running a task on the aggregate project will also run it on the aggregated projects.
// dependsOn: a project depends on code in another project.
// without dependsOn, you'll get a compiler error: "object bar is not a member of package
// com.alvinalexander".
lazy val root = (project in file("."))
.aggregate(subproject1, subproject2)
.dependsOn(subproject1, subproject2)
这里可以看到subproject1和subproject2分别代表两个子项目的SBT,然后定义root为父目录的SBT。 其中dependsOn是指依赖关系,其依赖项目的代码可以直接使用; 而aggregate是指联合关系,如果父项目执行compile等任务,那么两个子项目也会相应执行该任务。
我们在父项目中写一个对象来测试是否可以实现代码依赖:
package ink.baixin.scalalearning.sbt
import ink.baixin.scalalearning.subproject2._
import ink.baixin.scalalearning.subproject1._
object MultipleProjectsExample extends App {
println(Foo("I'm a Foo"))
println(Bar("I'm a Bar"))
}
结果:
Foo(I'm a Foo)
Bar(I'm a Bar)
以上是在 Scala 中创建多模块项目的示例。 在实际开发中,一些大型项目可能内部业务复杂,模块较多。 那么我们可以考虑把它们分成几个小的部分,将公共的部分分离出来作为一个依赖的项目java jar 最小依赖包,这样可以让我们的代码更加的清晰和条理。
如何指定主类
可以通过添加以下内容来设置主类:
// set the main class for packaging the main jar
mainClass in (Compile, packageBin) := Some("ink.baixin.scalalearning.sbt.Hello")
// set the main class for the main 'sbt run' task
mainClass in (Compile, run) := Some("ink.baixin.scalalearning.sbt.Hello")
如何使用来自github的代码
现在我们想直接使用一个github开源库的代码java jar 最小依赖包,可以这样配置:
lazy val githubProject = RootProject(uri("https://github.com/Trigl/akka-learning.git"))
lazy val root = (project in file(".")).dependsOn(githubProject)
这样设置之后,就可以在代码中直接引用开源库的代码了。
如何在 SBT 中设置 Ivy 存储库
可以使用 build.sbt 中的解析器添加 Ivy 存储库:
resolvers += "repository name" at "location"
举例如下:
resolvers += "Java.net Maven2 Repository" at "http://download.java.net/maven/2/"
当然你也可以用Seq添加多个仓库:
resolvers ++= Seq(
"Typesafe" at "http://repo.typesafe.com/typesafe/releases/",
"Java.net Maven2 Repository" at "http://download.java.net/maven/2/"
)
如何设置SBT的日志级别
这很简单,只需添加以下内容:
logLevel := Level.Info
如何部署一个完整的可执行Jar包
这个问题看起来很容易。 你可以直接使用 sbt 包。 其实并没有用,因为package命令只会保存本项目的src/main/scala、src/main/java、src/main/resources下的文件。 它被打包成Jar包,但是有两个重要且必要的内容不会被打包。 这两个类别是:
那么如何解决这个问题呢,推荐的方法是使用开源工具sbt-assembly来打包。 该项目将包括所有需要的依赖项,并部署一个可以在分布式环境中执行的 Jar 包。
那么如何使用呢,首先在项目目录下添加文件assembly.sbt:
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.7")
然后执行sbt assembly打包,就这么简单,当然你可以在build.sbt中添加一些更详细的配置。
如何发布你的代码
您可以使用命令 publish 或 publishLocal 来发布代码。 发布是将项目发布到远程仓库,所以需要设置远程仓库的地址。 现在我设置一个本地路径作为仓库地址:
// publish project to a remote repository
publishTo in ThisBuild := Some(Resolver.file("file", new File("/Users/will/tmp")))
然后执行sbt publish,结果如下:
...
[info] published scala-learning_2.12 to /Users/will/tmp/scala-learning/scala-learning_2.12/0.1.part/scala-learning_2.12-0.1.pom
[info] published scala-learning_2.12 to /Users/will/tmp/scala-learning/scala-learning_2.12/0.1.part/scala-learning_2.12-0.1.jar
[info] published scala-learning_2.12 to /Users/will/tmp/scala-learning/scala-learning_2.12/0.1.part/scala-learning_2.12-0.1-sources.jar
[info] published scala-learning_2.12 to /Users/will/tmp/scala-learning/scala-learning_2.12/0.1.part/scala-learning_2.12-0.1-javadoc.jar
[info] publish commited: moved /Users/will/tmp/scala-learning/scala-learning_2.12/0.1.part
[info] to /Users/will/tmp/scala-learning/scala-learning_2.12/0.1
[success] Total time: 1 s, completed Aug 22, 2018 4:13:46 PM
另一个发布命令是publishLocal,它将项目发布到本地ivy仓库。 这个包在本机其他项目中是可以直接调用的,只要在其他项目的build.sbt中写好这个包的依赖即可,如下;
libraryDependencies += "scala-learning" %% "scala-learning" % "0.1"
参考
Scala 食谱
build.sbt 例子
项目源码