自己动手在写一个简单多模块项目,从头到尾遇到不少问题,所以记录下。

这里依赖是拆分的,每个模块有自己的依赖和配置,统一由根项目管理。最终sbt多模块打包成jar并发布到本地maven仓库由benchmark项目引用。

1. 创建一个sbt项目,命名dlsRpc

这是一个根项目,包含配置文件和依赖定义。


image.png

2. 创建三个子模块

根项目dlsRpc本身不包含任何代码(忽略benchmark模块,这个是maven项目)


image.png

3. 在build.sbt增加构建配置

将几个模块关联起来

import Dependencies.Versions

//工程通用配置
lazy val commonSettings = Seq(
  organization := "io.growing",
  version := "1.0.13",
  scalaVersion := Versions.scala212,
  Dependencies.commons
)

//根项目配置,benchmark与本项目无直接依赖关系,所以忽略
//benchmark以jar包的形式依赖dlsRpc
lazy val root = Project(id = "dlsRpc", base = file("."))
  .settings(commonSettings).aggregate(consuls, core, commons)

//核心实现,依赖consuls,commons
lazy val core = Project(id = "dlsRpc-core", base = file("dlsRpc-core"))
  .settings(commonSettings, Dependencies.core).dependsOn(commons, consuls)

//服务注册发现,可依赖commons但不能反向依赖core
lazy val consuls = Project(id = "dlsRpc-consul", base = file("dlsRpc-consul"))
  .settings(commonSettings, Dependencies.consuls).dependsOn(commons)

//通用工具和隐式对象,不可反向依赖core,consuls
lazy val commons = Project(id = "dlsRpc-common", base = file("dlsRpc-common"))
  .settings(commonSettings, Dependencies.common)

javacOptions ++= Seq("-encoding", "UTF-8")
javaOptions in run += "-Xmx1G"

//编译路径
//windows不能使用git cmd 命令行打包,需要使用sbt

//发布到本地maven仓库的时候,允许覆盖jar。
//发布到仓库后本地maven才能引入,而不再需要加入lib文件
publishM2Configuration := publishM2Configuration.value.withOverwrite(true)
publishConfiguration := publishConfiguration.value.withOverwrite(true)
publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true)

这里四个模块都使用了通用设置commonSettings,并且根项目dlsRpc依赖下面三个子模块,使用aggregate 设置根项目包含三个模块,每个模块使用dependsOn设置模块依赖,一般模块的id和文件名是相同的,这里都使用了dlsRpc-*,如果深究这里区别是文件名称对应项目在电脑上存储的文件名称,id是项目导入后显示的模块名称。
settings传入的是可变参数,对于每个模块可以设置自己的依赖,如Dependencies.*,当然还可以加入其它参数。
两个方法源码如下

image.png

可见 SettingsDefinition* ProjectReference*均是一个scala的可变长参数,可传入多个值, ss: _*的作用是将可变长参数以“个体”的形式作为参数传入,如果不使用 :_*则会以整体的形式传入。
Dependencies是一个object,包含所有依赖。
image.png

各个模块依赖的组合方式如下
import sbt.Keys.libraryDependencies
import sbt._

object Dependencies {

  object Versions {
    val scala212 = "2.12.7"
    val log4j = "2.11.1"
    val guava = "19.0"
    val guice = "3.0"
    val netty = "4.1.6.Final"
    val logging = "3.9.2"
    val slfj = "2.1.2"
    val protostuff = "1.0.12"
    val consul = "1.4.2"
    val log4j_api = "11.0"
    val cglib = "3.2.10"
    val config = "1.3.4"
  }

  object Compiles {

    lazy val config: ModuleID = "com.typesafe" % "config" % Versions.config

    lazy val cglib: ModuleID = "cglib" % "cglib-nodep" % Versions.cglib

    lazy val consulAPi: ModuleID = "com.ecwid.consul" % "consul-api" % Versions.consul

    lazy val guava: ModuleID = "com.google.guava" % "guava" % Versions.guava

    lazy val guice: ModuleID = "com.google.inject" % "guice" % Versions.guice
   
    lazy val log4j2: Seq[ModuleID] = Seq(
      "org.apache.logging.log4j" %% "log4j-api-scala" % Versions.log4j_api,
      "org.apache.logging.log4j" % "log4j-api" % Versions.log4j,
      "org.apache.logging.log4j" % "log4j-core" % Versions.log4j,
      "org.apache.logging.log4j" % "log4j-slf4j-impl" % Versions.log4j)

    lazy val protostuff: Seq[ModuleID] = Seq(
      "com.dyuproject.protostuff" % "protostuff-core" % Versions.protostuff,
      "com.dyuproject.protostuff" % "protostuff-runtime" % Versions.protostuff)

    lazy val netty: Seq[ModuleID] = Seq(
      "io.netty" % "netty-codec-http2" % Versions.netty,
      "io.netty" % "netty-handler" % Versions.netty)

    lazy val log: ModuleID =
      "com.typesafe.scala-logging" %% "scala-logging" % Versions.logging
  }

  import Compiles._

  //RPC调用
  val core = libraryDependencies ++= protostuff ++ netty ++ Seq(guice, cglib)

  //服务注册发现
  val consuls = libraryDependencies ++= Seq(consulAPi)

  //配置、工具、常量
  val common = libraryDependencies ++= Seq(config)

  //通用依赖
  val commons = libraryDependencies ++= log4j2 ++ Seq(log, guava)

}

maven项目benchmark中引入多模块jar

maven项目不影响这里的sbt构建,这个项目在哪都可以。
sbt构建时不会编译maven项目,所以maven子项目需要单独编译。这里没有设置仓库,默认使用publishM2将把jar包发布到/User/userName/.m2/repository/io/growing/,这里IDEA的maven本地仓库也要改成这个。
Scala命令行使用如下

image.png

这里如果修改了版本号,那么需要关闭这个窗口重新打开,新版本号才会在打包时生效。

如下图所示,默认携带了Scala的版本后缀


image.png

在benchmark的pom.xml文件中引用jar


image.png

注意:出现maven找不到jar中的依赖,可能是打包没有将依赖pom打进去,需要使用sbt的publishM2 插件,不然maven仓库无法读取到依赖。

在使用publishM2的时候可能出现无法写入到本地


image.png

这是因为相同版本号只能存在一个,虽然配置build.sbt覆盖写,但是好像并不会生效,目前还没解决,所以采用修改版本即可。版本号就是通用配置中的version属性

image.png

完整的项目结构


image.png

具体源码查看一个简单的Scala RPC