Scala:如何跨应用程序使用全局配置案例 class
Scala : How to use Global Config case class across application
我是 scala 的新手,刚开始使用我的 scala 第一个应用程序。
我已经在资源文件夹下定义了我的配置文件,application.conf
projectname{
"application" {
"host":127.0.0.1
"port":8080
}
}
我写了一个配置解析器文件来从配置文件解析到案例class
case class AppConfig (config: Config) {
val host = config.getString("projectname.application.host")
val port = config.getInt("projectname.application.port")
}
在我的 grpc 服务器文件中,我已将配置声明为
val config = AppConfig(ConfigFactory.load("application.conf"))
我想跨应用程序使用这个配置变量,而不是每次都加载 application.conf 文件。
我想要一个 bootstrap 函数来一次性解析此配置,使其在整个应用程序中可用
您可以使用 PureConfig 自动执行此操作。
为您添加 Pure Config build.sbt
:
libraryDependencies += "com.github.pureconfig" %% "pureconfig" % "0.11.0"
并重新加载 sbt shell 并更新您的依赖项。
现在,假设您有以下 resource.conf
文件:
host: example.com
port: 80
user: admin
password: admin_password
您可以定义一个名为 AppConfig
:
的案例 class
case class AppConfig(
host: String,
port: Int,
user: String,
password: String
)
并使用 loadConfig
方法创建一个实例,填充应用程序配置:
import pureconfig.generic.auto._
val errorsOrConfig: Either[ConfigReaderFailures, AppConfig] = pureconfig.loadConfig[AppConfig]
这 returns 要么是错误,要么是您的 AppConfig,具体取决于配置本身中的值。
例如,如果上面 port
的值将是 eighty
,而不是 80,您将得到一个详细的错误,说第二个配置行(带有 port: eighty
)包含一个字符串,但唯一有效的预期类型是数字:
ConfigReaderFailures(
ConvertFailure(
reason = WrongType(
foundType = STRING,
expectedTypes = Set(NUMBER)
),
location = Some(
ConfigValueLocation(
new URL("file:~/repos/example-project/target/scala-2.12/classes/application.conf"),
lineNumber = 2
)
),
path = "port"
)
)
如果您想获得 AppConfig
而不是 Either,可以使用 loadConfigOrThrow
。
在您的应用程序启动时(尽可能靠近您的主函数)加载一次此配置后,您可以使用依赖注入将其传递给所有其他 classes,只需通过在构造函数中传递 AppConfig。
如果您想使用 MacWire, as Krzysztof suggested in one of his options, you can see my answer here.
将您的逻辑 class(和其他服务)与配置案例 class 连接起来
普通示例(没有 MacWire),如下所示:
package com.example
import com.example.config.AppConfig
object HelloWorld extends App {
val config: AppConfig = pureconfig.loadConfigOrThrow[AppConfig]
val logic = new Logic(config)
}
class Logic(config: AppConfig) {
// do something with config
}
AppConfig 定义在 AppConfig.scala
package com.example.config
case class AppConfig(
host: String,
port: Int,
user: String,
password: String
)
作为奖励,当您在 IDE 中使用此配置变量时,您将获得代码补全。
此外,您的配置可能是根据 supported types 构建的,例如 String、Boolean、Int 等,但也可能是根据支持的类型构建的其他 classes(这是因为 case class 表示一个包含数据的值对象),以及支持类型的列表和选项。
这允许您 "class up" 一个复杂的配置文件并获得代码完成。例如,在 application.conf
:
name: hotels_best_dishes
host: "https://example.com"
port: 80
hotels: [
"Club Hotel Lutraky Greece",
"Four Seasons",
"Ritz",
"Waldorf Astoria"
]
min-duration: 2 days
currency-by-location {
us = usd
england = gbp
il = nis
}
accepted-currency: [usd, gbp, nis]
application-id: 00112233-4455-6677-8899-aabbccddeeff
ssh-directory: /home/whoever/.ssh
developer: {
name: alice,
age: 20
}
然后在您的代码中定义一个配置案例class:
import java.net.URL
import java.util.UUID
import scala.concurrent.duration.FiniteDuration
import pureconfig.generic.EnumCoproductHint
import pureconfig.generic.auto._
case class Person(name: String, age: Int)
sealed trait Currency
case object Usd extends Currency
case object Gbp extends Currency
case object Nis extends Currency
object Currency {
implicit val currencyHint: EnumCoproductHint[Currency] = new EnumCoproductHint[Currency]
}
case class Config(
name: String,
host: URL,
port: Int,
hotels: List[String],
minDuration: FiniteDuration,
currencyByLocation: Map[String, Currency],
acceptedCurrency: List[Currency],
applicationId: UUID,
sshDirectory: java.nio.file.Path,
developer: Person
)
并加载它:
val config: Config = pureconfig.loadConfigOrThrow[Config]
您尝试实现的模式称为依赖注入。来自 Martin Fowler 关于此主题的 post
The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface.
在依赖注入工具中注册此配置实例,例如 Guice。
class AppModule(conf: AppConfiguration) extends AbstractModule {
override def configure(): Unit = {
bind(classOf[AppConfiguration]).toInstance(conf)
}
}
....
// somewhere in the code
import com.google.inject.Inject
class FooClass @Inject() (config: AppConfiguration)
您应该将字段定义为案例的参数 class。
final case class AppConfig(host: String, port: Int)
然后重载伴生对象的 apply 方法
object AppConfig {
def apply(config: Config): AppConfig = {
val host = config.getString("projectname.application.host")
val port = config.getInt("projectname.application.port")
AppConfig(host, port)
}
}
然而,使用 case classes 处理配置的最简单方法是使用 pureconfig.
I want to use this config variable across application, rather than loading application.conf file everytime.
就放在object
里就好了,比如
object MyConfig {
lazy val config = AppConfig(ConfigFactory.load("application.conf"))
}
I want to have one bootstrap function which will parse this config one time, making it available across application
一旦调用 MyConfig.config
,它只会加载一次 - 因为 object
是一个 Singleton。所以不需要特殊的bootstrap。
有一些可能性可以解决您的问题:
使用像 guice 这样的运行时依赖注入框架。您可以使用 extension for scala.
使用implicits来处理。您只需要创建一个对象,它将保存您的隐式配置:
object Implicits {
implicit val config = AppConfig(ConfigFactory.load("application.conf"))
}
然后您可以在需要时将 implicit config: Config
添加到您的参数列表中:
def process(n: Int)(implicit val config: Config) = ??? //as method parameter
case class Processor(n: Int)(implicit val config: AppConfig) //or as class field
并像这样使用它:
import Implicits._
process(5) //config passed implicitly here
Processor(10) //and here
它的一大优点是您可以手动通过 config
进行测试:
process(5)(config)
这种方法的缺点是,在你的应用程序中有很多隐式解析,会使编译变慢,但如果你的应用程序不庞大,这应该不是问题。
将 config 设置为您的 类 字段(称为构造函数注入)。
class Foo(config: Config).
然后您可以 wire-up 手动添加您的依赖项,例如:
val config: AppConfig = AppConfig()
val foo = Foo(config) //you need to pass config manually to constructors in your object graph
或者你可以使用一个可以为你自动化的框架,比如 macwire:
val config = wire[AppConfig]
val foo = wire[Foo]
您可以使用名为 cake-pattern 的模式。它适用于 small-sized 应用程序,但您的应用程序越大,这种方法就越笨拙。
什么是 NOT 一个好的方法是像这样使用全局单例:
object ConfigHolder {
val Config: AppConfig = ???
}
然后像这样使用它:
def process(n: Int) = {
val host = ConfigHolder.Config.host // anti-pattern
}
这很糟糕,因为它使模拟您的测试配置变得非常困难,并且整个测试过程变得笨拙。
在我看来,如果你的应用不是很大,你应该使用 implicits。
如果您想了解有关此主题的更多信息,请查看 this great guide。
我是 scala 的新手,刚开始使用我的 scala 第一个应用程序。
我已经在资源文件夹下定义了我的配置文件,application.conf
projectname{
"application" {
"host":127.0.0.1
"port":8080
}
}
我写了一个配置解析器文件来从配置文件解析到案例class
case class AppConfig (config: Config) {
val host = config.getString("projectname.application.host")
val port = config.getInt("projectname.application.port")
}
在我的 grpc 服务器文件中,我已将配置声明为
val config = AppConfig(ConfigFactory.load("application.conf"))
我想跨应用程序使用这个配置变量,而不是每次都加载 application.conf 文件。
我想要一个 bootstrap 函数来一次性解析此配置,使其在整个应用程序中可用
您可以使用 PureConfig 自动执行此操作。
为您添加 Pure Config build.sbt
:
libraryDependencies += "com.github.pureconfig" %% "pureconfig" % "0.11.0"
并重新加载 sbt shell 并更新您的依赖项。
现在,假设您有以下 resource.conf
文件:
host: example.com
port: 80
user: admin
password: admin_password
您可以定义一个名为 AppConfig
:
case class AppConfig(
host: String,
port: Int,
user: String,
password: String
)
并使用 loadConfig
方法创建一个实例,填充应用程序配置:
import pureconfig.generic.auto._
val errorsOrConfig: Either[ConfigReaderFailures, AppConfig] = pureconfig.loadConfig[AppConfig]
这 returns 要么是错误,要么是您的 AppConfig,具体取决于配置本身中的值。
例如,如果上面 port
的值将是 eighty
,而不是 80,您将得到一个详细的错误,说第二个配置行(带有 port: eighty
)包含一个字符串,但唯一有效的预期类型是数字:
ConfigReaderFailures(
ConvertFailure(
reason = WrongType(
foundType = STRING,
expectedTypes = Set(NUMBER)
),
location = Some(
ConfigValueLocation(
new URL("file:~/repos/example-project/target/scala-2.12/classes/application.conf"),
lineNumber = 2
)
),
path = "port"
)
)
如果您想获得 AppConfig
而不是 Either,可以使用 loadConfigOrThrow
。
在您的应用程序启动时(尽可能靠近您的主函数)加载一次此配置后,您可以使用依赖注入将其传递给所有其他 classes,只需通过在构造函数中传递 AppConfig。
如果您想使用 MacWire, as Krzysztof suggested in one of his options, you can see my answer here.
将您的逻辑 class(和其他服务)与配置案例 class 连接起来普通示例(没有 MacWire),如下所示:
package com.example
import com.example.config.AppConfig
object HelloWorld extends App {
val config: AppConfig = pureconfig.loadConfigOrThrow[AppConfig]
val logic = new Logic(config)
}
class Logic(config: AppConfig) {
// do something with config
}
AppConfig 定义在 AppConfig.scala
package com.example.config
case class AppConfig(
host: String,
port: Int,
user: String,
password: String
)
作为奖励,当您在 IDE 中使用此配置变量时,您将获得代码补全。
此外,您的配置可能是根据 supported types 构建的,例如 String、Boolean、Int 等,但也可能是根据支持的类型构建的其他 classes(这是因为 case class 表示一个包含数据的值对象),以及支持类型的列表和选项。
这允许您 "class up" 一个复杂的配置文件并获得代码完成。例如,在 application.conf
:
name: hotels_best_dishes
host: "https://example.com"
port: 80
hotels: [
"Club Hotel Lutraky Greece",
"Four Seasons",
"Ritz",
"Waldorf Astoria"
]
min-duration: 2 days
currency-by-location {
us = usd
england = gbp
il = nis
}
accepted-currency: [usd, gbp, nis]
application-id: 00112233-4455-6677-8899-aabbccddeeff
ssh-directory: /home/whoever/.ssh
developer: {
name: alice,
age: 20
}
然后在您的代码中定义一个配置案例class:
import java.net.URL
import java.util.UUID
import scala.concurrent.duration.FiniteDuration
import pureconfig.generic.EnumCoproductHint
import pureconfig.generic.auto._
case class Person(name: String, age: Int)
sealed trait Currency
case object Usd extends Currency
case object Gbp extends Currency
case object Nis extends Currency
object Currency {
implicit val currencyHint: EnumCoproductHint[Currency] = new EnumCoproductHint[Currency]
}
case class Config(
name: String,
host: URL,
port: Int,
hotels: List[String],
minDuration: FiniteDuration,
currencyByLocation: Map[String, Currency],
acceptedCurrency: List[Currency],
applicationId: UUID,
sshDirectory: java.nio.file.Path,
developer: Person
)
并加载它:
val config: Config = pureconfig.loadConfigOrThrow[Config]
您尝试实现的模式称为依赖注入。来自 Martin Fowler 关于此主题的 post
The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface.
在依赖注入工具中注册此配置实例,例如 Guice。
class AppModule(conf: AppConfiguration) extends AbstractModule {
override def configure(): Unit = {
bind(classOf[AppConfiguration]).toInstance(conf)
}
}
....
// somewhere in the code
import com.google.inject.Inject
class FooClass @Inject() (config: AppConfiguration)
您应该将字段定义为案例的参数 class。
final case class AppConfig(host: String, port: Int)
然后重载伴生对象的 apply 方法
object AppConfig {
def apply(config: Config): AppConfig = {
val host = config.getString("projectname.application.host")
val port = config.getInt("projectname.application.port")
AppConfig(host, port)
}
}
然而,使用 case classes 处理配置的最简单方法是使用 pureconfig.
I want to use this config variable across application, rather than loading application.conf file everytime.
就放在object
里就好了,比如
object MyConfig {
lazy val config = AppConfig(ConfigFactory.load("application.conf"))
}
I want to have one bootstrap function which will parse this config one time, making it available across application
一旦调用 MyConfig.config
,它只会加载一次 - 因为 object
是一个 Singleton。所以不需要特殊的bootstrap。
有一些可能性可以解决您的问题:
使用像 guice 这样的运行时依赖注入框架。您可以使用 extension for scala.
使用implicits来处理。您只需要创建一个对象,它将保存您的隐式配置:
object Implicits { implicit val config = AppConfig(ConfigFactory.load("application.conf")) }
然后您可以在需要时将
implicit config: Config
添加到您的参数列表中:def process(n: Int)(implicit val config: Config) = ??? //as method parameter case class Processor(n: Int)(implicit val config: AppConfig) //or as class field
并像这样使用它:
import Implicits._ process(5) //config passed implicitly here Processor(10) //and here
它的一大优点是您可以手动通过
config
进行测试:process(5)(config)
这种方法的缺点是,在你的应用程序中有很多隐式解析,会使编译变慢,但如果你的应用程序不庞大,这应该不是问题。
将 config 设置为您的 类 字段(称为构造函数注入)。
class Foo(config: Config).
然后您可以 wire-up 手动添加您的依赖项,例如:
val config: AppConfig = AppConfig() val foo = Foo(config) //you need to pass config manually to constructors in your object graph
或者你可以使用一个可以为你自动化的框架,比如 macwire:
val config = wire[AppConfig] val foo = wire[Foo]
您可以使用名为 cake-pattern 的模式。它适用于 small-sized 应用程序,但您的应用程序越大,这种方法就越笨拙。
什么是 NOT 一个好的方法是像这样使用全局单例:
object ConfigHolder {
val Config: AppConfig = ???
}
然后像这样使用它:
def process(n: Int) = {
val host = ConfigHolder.Config.host // anti-pattern
}
这很糟糕,因为它使模拟您的测试配置变得非常困难,并且整个测试过程变得笨拙。
在我看来,如果你的应用不是很大,你应该使用 implicits。
如果您想了解有关此主题的更多信息,请查看 this great guide。